diff --git a/agent_framework_oci/libs/agent_framework/build/lib/agent_framework/routing/__pycache__/enterprise_router.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/build/lib/agent_framework/routing/__pycache__/enterprise_router.cpython-313.pyc index b805d8d4..96d1c56c 100644 Binary files a/agent_framework_oci/libs/agent_framework/build/lib/agent_framework/routing/__pycache__/enterprise_router.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/build/lib/agent_framework/routing/__pycache__/enterprise_router.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/build/lib/agent_framework/routing/enterprise_router.py b/agent_framework_oci/libs/agent_framework/build/lib/agent_framework/routing/enterprise_router.py index ef8a569e..cdabbd9d 100644 --- a/agent_framework_oci/libs/agent_framework/build/lib/agent_framework/routing/enterprise_router.py +++ b/agent_framework_oci/libs/agent_framework/build/lib/agent_framework/routing/enterprise_router.py @@ -630,9 +630,24 @@ class EnterpriseRouter: if state_decision: tx_status = str(state.get("transaction_status") or "").strip().upper() - # Confirmation is the only transaction input with absolute precedence: - # an explicit yes/no answers the confirmation contract itself. + # Explicit abandonment must be evaluated before confirmation + # consumption. Otherwise an utterance such as + # "não quero cancelar; quero apenas consultar meus serviços" can be + # trapped by the confirmation state and never reach ABANDON. A plain + # yes/no still keeps deterministic confirmation precedence because it + # does not pass the abandonment gate. if tx_status == "AWAITING_CONFIRMATION": + if self._looks_like_explicit_abandonment(str(text)): + abandonment = await self._transaction_state_interruption_candidate( + state, + text=str(text), + state_decision=state_decision, + abandon_only=True, + ) + if abandonment is not None: + await self._emit(abandonment, state) + return abandonment + consumed = await self._transaction_parameter_precedence( state, text=str(text), state_decision=state_decision ) @@ -687,6 +702,21 @@ class EnterpriseRouter: next_state=tx_status, ) if tx_status == "AWAITING_CONFIRMATION": + if self._looks_like_explicit_abandonment(str(text)): + abandonment = await self._transaction_state_interruption_candidate( + state, + text=str(text), + state_decision=synthetic, + abandon_only=True, + ) + if abandonment is not None: + abandonment.metadata = { + **(abandonment.metadata or {}), + "transaction_state_recovered": True, + } + await self._emit(abandonment, state) + return abandonment + consumed = await self._transaction_parameter_precedence( state, text=str(text), state_decision=synthetic ) @@ -846,11 +876,12 @@ class EnterpriseRouter: ) -> RouteDecision | None: """Try to consume the turn under the active transaction contract first. - AWAITING_CONFIRMATION consumes an explicit confirmation before any shift - classification. COLLECTING_PARAMETERS also has precedence: if at least one - pending parameter can be extracted, the active transaction keeps ownership - of the turn. Semantic intent-shift is evaluated only when extraction returns - no usable pending parameter. + In AWAITING_CONFIRMATION, an explicit ABANDON candidate is classified + before confirmation consumption; plain yes/no responses still keep + deterministic confirmation precedence because they do not pass the + abandonment gate. In COLLECTING_PARAMETERS, ABANDON is likewise checked + before extraction, while SHIFT remains after parameter extraction so valid + referential/value answers are not stolen from the active transaction. """ tx_status = str(state.get("transaction_status") or "").strip().upper() if tx_status == "AWAITING_CONFIRMATION": diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/__pycache__/__init__.cpython-313.pyc index 9e6ab7b1..1d80024a 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/__pycache__/__init__.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/__pycache__/__init__.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/__pycache__/idempotency.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/__pycache__/idempotency.cpython-313.pyc index 419d753a..221978a7 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/__pycache__/idempotency.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/__pycache__/idempotency.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/cache/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/cache/__pycache__/__init__.cpython-313.pyc index 570985a0..250db909 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/cache/__pycache__/__init__.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/cache/__pycache__/__init__.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/cache/__pycache__/cache.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/cache/__pycache__/cache.cpython-313.pyc index d1242ed3..e24b9c71 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/cache/__pycache__/cache.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/cache/__pycache__/cache.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/__init__.cpython-313.pyc index a251e689..a601bbbb 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/__init__.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/__init__.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/settings.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/settings.cpython-313.pyc index 4175dd4c..74881968 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/settings.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/config/__pycache__/settings.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/gateways/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/gateways/__pycache__/__init__.cpython-313.pyc index fb011b5f..7f83e0f8 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/gateways/__pycache__/__init__.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/gateways/__pycache__/__init__.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/gateways/__pycache__/mcp_gateway_client.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/gateways/__pycache__/mcp_gateway_client.cpython-313.pyc index 6c583696..f7c8b34b 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/gateways/__pycache__/mcp_gateway_client.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/gateways/__pycache__/mcp_gateway_client.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/__init__.cpython-313.pyc index 79439883..335a5e10 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/__init__.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/__init__.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/mcp_mapper.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/mcp_mapper.cpython-313.pyc index 1fffb81f..d121f166 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/mcp_mapper.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/mcp_mapper.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/models.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/models.cpython-313.pyc index b015c14b..46507e7c 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/models.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/models.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/resolver.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/resolver.cpython-313.pyc index 4e40edf3..0815e5b5 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/resolver.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/identity/__pycache__/resolver.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/__init__.cpython-313.pyc index 914bae72..e61826b7 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/__init__.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/__init__.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/base.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/base.cpython-313.pyc index 05e9824a..13d5bf27 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/base.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/base.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/structured_output.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/structured_output.cpython-313.pyc index 83898b8e..22a37660 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/structured_output.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/structured_output.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/types.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/types.cpython-313.pyc index 94ead776..55dada43 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/types.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/llm/__pycache__/types.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/__init__.cpython-313.pyc index 21ce5a3a..6c24af46 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/__init__.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/__init__.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/client.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/client.cpython-313.pyc index 07e23de3..283f1c7b 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/client.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/client.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/models.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/models.cpython-313.pyc index a8a2df48..509b1e79 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/models.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/models.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/registry.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/registry.cpython-313.pyc index 9913bbf6..49b49a4d 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/registry.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/registry.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_policy.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_policy.cpython-313.pyc index 3a3a8c72..a14478d4 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_policy.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_policy.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_router.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_router.cpython-313.pyc index ecc2d0cf..f77037b9 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_router.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/mcp/__pycache__/tool_router.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/__init__.cpython-313.pyc index f8e01ee5..05dbbc5e 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/__init__.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/__init__.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/message_history.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/message_history.cpython-313.pyc index ade5aed4..bcb131bd 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/message_history.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/message_history.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_memory.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_memory.cpython-313.pyc index 3e8188b1..65e00cee 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_memory.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_memory.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_store.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_store.cpython-313.pyc index 5f3ce0d4..f3f49c14 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_store.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/memory/__pycache__/summary_store.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/models/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/models/__pycache__/__init__.cpython-313.pyc index 3fa4f458..9b41ef63 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/models/__pycache__/__init__.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/models/__pycache__/__init__.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/models/__pycache__/session.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/models/__pycache__/session.cpython-313.pyc index 142868d5..e2dde3d2 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/models/__pycache__/session.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/models/__pycache__/session.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/persistence/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/persistence/__pycache__/__init__.cpython-313.pyc index 733a8d0f..bd1c48a6 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/persistence/__pycache__/__init__.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/persistence/__pycache__/__init__.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/persistence/__pycache__/sqlite_store.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/persistence/__pycache__/sqlite_store.cpython-313.pyc index cb0ebebe..7001b6a9 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/persistence/__pycache__/sqlite_store.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/persistence/__pycache__/sqlite_store.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/__init__.cpython-313.pyc index 3c9f6b05..a0dd31bb 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/__init__.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/__init__.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/config_loader.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/config_loader.cpython-313.pyc index fcbbfdfb..24f2fc44 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/config_loader.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/config_loader.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/continuity.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/continuity.cpython-313.pyc index 3bb32a12..f0b371f4 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/continuity.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/continuity.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/enterprise_router.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/enterprise_router.cpython-313.pyc index 53b18084..e3153e77 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/enterprise_router.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/enterprise_router.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/models.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/models.cpython-313.pyc index 6b7c1641..729d5e50 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/models.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/models.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/multi_intent.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/multi_intent.cpython-313.pyc index 57aed7e1..ddb9f08b 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/multi_intent.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/multi_intent.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/pending_topics.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/pending_topics.cpython-313.pyc index 34d35240..849f5fe7 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/pending_topics.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/__pycache__/pending_topics.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/enterprise_router.py b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/enterprise_router.py index ef8a569e..45f384d9 100644 --- a/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/enterprise_router.py +++ b/agent_framework_oci/libs/agent_framework/src/agent_framework/routing/enterprise_router.py @@ -630,9 +630,24 @@ class EnterpriseRouter: if state_decision: tx_status = str(state.get("transaction_status") or "").strip().upper() - # Confirmation is the only transaction input with absolute precedence: - # an explicit yes/no answers the confirmation contract itself. + # Explicit abandonment must be evaluated before confirmation + # consumption. Otherwise an utterance such as + # "não quero cancelar; quero apenas consultar meus serviços" can be + # trapped by the confirmation state and never reach ABANDON. A plain + # yes/no still keeps deterministic confirmation precedence because it + # does not pass the abandonment gate. if tx_status == "AWAITING_CONFIRMATION": + if self._looks_like_explicit_abandonment(str(text)): + abandonment = await self._transaction_state_interruption_candidate( + state, + text=str(text), + state_decision=state_decision, + abandon_only=True, + ) + if abandonment is not None: + await self._emit(abandonment, state) + return abandonment + consumed = await self._transaction_parameter_precedence( state, text=str(text), state_decision=state_decision ) @@ -640,13 +655,24 @@ class EnterpriseRouter: await self._emit(consumed, state) return consumed - # Transaction parameter precedence is absolute while collecting: - # first let the active transaction try to consume the current turn. - # Only when NO pending parameter can be extracted do we ask the - # semantic classifier whether the user changed goals. This prevents - # value/name/reference answers (for example "a de 14,99") from being - # stolen by a semantically plausible but incompatible intent. + # During parameter collection, explicit abandonment has stronger + # precedence than parameter extraction. Otherwise an LLM parameter + # extractor can incorrectly "consume" phrases such as + # "esquece, não quero mais cancelar" and the ABANDON classifier is + # never reached. SHIFT remains after parameter extraction so a valid + # value/name/reference answer is not stolen by a plausible new intent. if tx_status == "COLLECTING_PARAMETERS": + if self._looks_like_explicit_abandonment(str(text)): + abandonment = await self._transaction_state_interruption_candidate( + state, + text=str(text), + state_decision=state_decision, + abandon_only=True, + ) + if abandonment is not None: + await self._emit(abandonment, state) + return abandonment + consumed = await self._transaction_parameter_precedence( state, text=str(text), state_decision=state_decision ) @@ -687,6 +713,21 @@ class EnterpriseRouter: next_state=tx_status, ) if tx_status == "AWAITING_CONFIRMATION": + if self._looks_like_explicit_abandonment(str(text)): + abandonment = await self._transaction_state_interruption_candidate( + state, + text=str(text), + state_decision=synthetic, + abandon_only=True, + ) + if abandonment is not None: + abandonment.metadata = { + **(abandonment.metadata or {}), + "transaction_state_recovered": True, + } + await self._emit(abandonment, state) + return abandonment + consumed = await self._transaction_parameter_precedence( state, text=str(text), state_decision=synthetic ) @@ -699,6 +740,21 @@ class EnterpriseRouter: return consumed if tx_status == "COLLECTING_PARAMETERS": + if self._looks_like_explicit_abandonment(str(text)): + abandonment = await self._transaction_state_interruption_candidate( + state, + text=str(text), + state_decision=synthetic, + abandon_only=True, + ) + if abandonment is not None: + abandonment.metadata = { + **(abandonment.metadata or {}), + "transaction_state_recovered": True, + } + await self._emit(abandonment, state) + return abandonment + consumed = await self._transaction_parameter_precedence( state, text=str(text), state_decision=synthetic ) @@ -846,11 +902,12 @@ class EnterpriseRouter: ) -> RouteDecision | None: """Try to consume the turn under the active transaction contract first. - AWAITING_CONFIRMATION consumes an explicit confirmation before any shift - classification. COLLECTING_PARAMETERS also has precedence: if at least one - pending parameter can be extracted, the active transaction keeps ownership - of the turn. Semantic intent-shift is evaluated only when extraction returns - no usable pending parameter. + In AWAITING_CONFIRMATION, an explicit ABANDON candidate is classified + before confirmation consumption; plain yes/no responses still keep + deterministic confirmation precedence because they do not pass the + abandonment gate. In COLLECTING_PARAMETERS, ABANDON is likewise checked + before extraction, while SHIFT remains after parameter extraction so valid + referential/value answers are not stolen from the active transaction. """ tx_status = str(state.get("transaction_status") or "").strip().upper() if tx_status == "AWAITING_CONFIRMATION": @@ -960,12 +1017,36 @@ class EnterpriseRouter: } return state_decision + @staticmethod + def _looks_like_explicit_abandonment(text: str) -> bool: + """Cheap, domain-neutral gate for semantic ABANDON classification. + + This is deliberately not the final decision. It only protects clear + abandonment utterances from being swallowed by parameter extraction; the + semantic classifier still decides whether the turn is actually ABANDON. + """ + import re + normalized = str(text or "").strip().lower() + if not normalized: + return False + cues = ( + r"\besquece(?:\s+isso|\s+essa|\s+esse|\s+tudo)?\b", + r"\bn[aã]o\s+quero\s+mais\b", + r"\bdesisti(?:\s+disso|\s+dessa|\s+desse)?\b", + r"\bdeixa\s+(?:isso|essa|esse|pra\s+l[aá]|para\s+l[aá])\b", + r"\bpode\s+parar\b", + r"\bquero\s+parar\b", + r"\bcancela\s+essa\s+solicita[cç][aã]o\b", + ) + return any(re.search(pattern, normalized, flags=re.IGNORECASE) for pattern in cues) + async def _transaction_state_interruption_candidate( self, state: dict[str, Any], *, text: str, state_decision: RouteDecision, + abandon_only: bool = False, ) -> RouteDecision | None: """Detecta semanticamente mudança de intenção durante uma transação. @@ -1038,18 +1119,30 @@ class EnterpriseRouter: else None ), } - system = ( - "Você decide apenas se o turno atual continua a transação ativa ou muda de intenção. " - "Use o significado da mensagem e o contexto transacional; não use palavras isoladas como regra. " - "A extração dos parâmetros pendentes já foi tentada antes desta etapa e não consumiu o turno. " - "Se ainda assim a mensagem for apenas uma resposta referencial/valor/nome ao dado pendente, retorne CONTINUE. " - "Se o usuário passou claramente a perseguir outro objetivo sem desistir explicitamente da ação atual, " - "retorne SHIFT e a nova intent permitida. " - "Se o usuário desistiu explicitamente da ação/transação atual, retorne ABANDON. " - "Quando ABANDON vier acompanhado de um novo objetivo, também informe intent e agent desse novo objetivo. " - "Quando for apenas abandono sem novo objetivo, intent e agent podem ser nulos. " - "Retorne somente JSON válido com decision, intent, agent, confidence, reason." - ) + if abandon_only: + system = ( + "Você decide somente se o usuário desistiu explicitamente da ação/transação ativa. " + "Use o significado completo da mensagem e o contexto transacional; não use palavras isoladas como regra. " + "Se houver desistência explícita da ação atual, retorne ABANDON. " + "Quando o abandono vier acompanhado de um novo objetivo, também informe intent e agent desse novo objetivo. " + "Quando for apenas abandono sem novo objetivo, intent e agent podem ser nulos. " + "Se a mensagem apenas fornece parâmetro, confirma contexto, ou somente muda/adiciona outro objetivo sem " + "desistir explicitamente da ação atual, retorne CONTINUE. " + "Retorne somente JSON válido com decision, intent, agent, confidence, reason." + ) + else: + system = ( + "Você decide apenas se o turno atual continua a transação ativa ou muda de intenção. " + "Use o significado da mensagem e o contexto transacional; não use palavras isoladas como regra. " + "A extração dos parâmetros pendentes já foi tentada antes desta etapa e não consumiu o turno. " + "Se ainda assim a mensagem for apenas uma resposta referencial/valor/nome ao dado pendente, retorne CONTINUE. " + "Se o usuário passou claramente a perseguir outro objetivo sem desistir explicitamente da ação atual, " + "retorne SHIFT e a nova intent permitida. " + "Se o usuário desistiu explicitamente da ação/transação atual, retorne ABANDON. " + "Quando ABANDON vier acompanhado de um novo objetivo, também informe intent e agent desse novo objetivo. " + "Quando for apenas abandono sem novo objetivo, intent e agent podem ser nulos. " + "Retorne somente JSON válido com decision, intent, agent, confidence, reason." + ) user = { "message": text, "transaction": transaction_context, @@ -1074,7 +1167,10 @@ class EnterpriseRouter: return None decision_kind = str(data.get("decision") or "").strip().upper() - if decision_kind not in {"SHIFT", "ABANDON"}: + if abandon_only: + if decision_kind != "ABANDON": + return None + elif decision_kind not in {"SHIFT", "ABANDON"}: return None confidence = float(data.get("confidence") or 0.0) if confidence < self.intent_shift_threshold: diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/__init__.cpython-313.pyc index 0a6e6f46..8e3def81 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/__init__.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/__init__.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/agent_runtime.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/agent_runtime.cpython-313.pyc index 2fa877d0..468fe737 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/agent_runtime.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/agent_runtime.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/transaction_parameters.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/transaction_parameters.cpython-313.pyc index 359fe755..6286ad47 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/transaction_parameters.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/runtime/__pycache__/transaction_parameters.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/__init__.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/__init__.cpython-313.pyc index 1ea33ed2..7ebd04d5 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/__init__.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/__init__.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/graph.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/graph.cpython-313.pyc index b7834f57..ba9b7462 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/graph.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/graph.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/input_contract.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/input_contract.cpython-313.pyc index e81dd4bf..09378f73 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/input_contract.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/input_contract.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/models.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/models.cpython-313.pyc index b7a0b9f8..f8e30f6b 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/models.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/models.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/registry.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/registry.cpython-313.pyc index 3f248383..1b0c21f3 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/registry.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/registry.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/repository.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/repository.cpython-313.pyc index f14e44c0..68c9663b 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/repository.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/repository.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/runtime.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/runtime.cpython-313.pyc index 5386cb34..d1966752 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/runtime.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/runtime.cpython-313.pyc differ diff --git a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/tool_executor.cpython-313.pyc b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/tool_executor.cpython-313.pyc index d5b2da55..e008c9c1 100644 Binary files a/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/tool_executor.cpython-313.pyc and b/agent_framework_oci/libs/agent_framework/src/agent_framework/workflows/__pycache__/tool_executor.cpython-313.pyc differ diff --git a/agent_framework_oci/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc index 9f26c74c..8cf5ca3e 100644 Binary files a/agent_framework_oci/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc and b/agent_framework_oci/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc differ diff --git a/agent_framework_oci/tests/__pycache__/test_paused_workflow_resume_precedence.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_paused_workflow_resume_precedence.cpython-313-pytest-9.0.2.pyc index d66326e1..50ebc16d 100644 Binary files a/agent_framework_oci/tests/__pycache__/test_paused_workflow_resume_precedence.cpython-313-pytest-9.0.2.pyc and b/agent_framework_oci/tests/__pycache__/test_paused_workflow_resume_precedence.cpython-313-pytest-9.0.2.pyc differ diff --git a/agent_framework_oci/tests/__pycache__/test_route_stickiness_transaction_shift.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_route_stickiness_transaction_shift.cpython-313-pytest-9.0.2.pyc index f1635a55..2d5daa31 100644 Binary files a/agent_framework_oci/tests/__pycache__/test_route_stickiness_transaction_shift.cpython-313-pytest-9.0.2.pyc and b/agent_framework_oci/tests/__pycache__/test_route_stickiness_transaction_shift.cpython-313-pytest-9.0.2.pyc differ diff --git a/agent_framework_oci/tests/__pycache__/test_transaction_confirmation_customer_facing.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_transaction_confirmation_customer_facing.cpython-313-pytest-9.0.2.pyc index d6311453..1be0638f 100644 Binary files a/agent_framework_oci/tests/__pycache__/test_transaction_confirmation_customer_facing.cpython-313-pytest-9.0.2.pyc and b/agent_framework_oci/tests/__pycache__/test_transaction_confirmation_customer_facing.cpython-313-pytest-9.0.2.pyc differ diff --git a/agent_framework_oci/tests/__pycache__/test_transaction_explicit_abandonment_and_structural_candidate.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_transaction_explicit_abandonment_and_structural_candidate.cpython-313-pytest-9.0.2.pyc index ec3b3223..93936225 100644 Binary files a/agent_framework_oci/tests/__pycache__/test_transaction_explicit_abandonment_and_structural_candidate.cpython-313-pytest-9.0.2.pyc and b/agent_framework_oci/tests/__pycache__/test_transaction_explicit_abandonment_and_structural_candidate.cpython-313-pytest-9.0.2.pyc differ diff --git a/agent_framework_oci/tests/__pycache__/test_transaction_parameter_llm_precedence.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_transaction_parameter_llm_precedence.cpython-313-pytest-9.0.2.pyc index d728688f..798a4a80 100644 Binary files a/agent_framework_oci/tests/__pycache__/test_transaction_parameter_llm_precedence.cpython-313-pytest-9.0.2.pyc and b/agent_framework_oci/tests/__pycache__/test_transaction_parameter_llm_precedence.cpython-313-pytest-9.0.2.pyc differ diff --git a/agent_framework_oci/tests/__pycache__/test_transaction_state_router_interruption.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/__pycache__/test_transaction_state_router_interruption.cpython-313-pytest-9.0.2.pyc index 5be25124..ca892940 100644 Binary files a/agent_framework_oci/tests/__pycache__/test_transaction_state_router_interruption.cpython-313-pytest-9.0.2.pyc and b/agent_framework_oci/tests/__pycache__/test_transaction_state_router_interruption.cpython-313-pytest-9.0.2.pyc differ diff --git a/agent_framework_oci/tests/test_transaction_explicit_abandonment_and_structural_candidate.py b/agent_framework_oci/tests/test_transaction_explicit_abandonment_and_structural_candidate.py index 9a7caf8d..c4cc9e52 100644 --- a/agent_framework_oci/tests/test_transaction_explicit_abandonment_and_structural_candidate.py +++ b/agent_framework_oci/tests/test_transaction_explicit_abandonment_and_structural_candidate.py @@ -13,6 +13,14 @@ class _RouterLLM: prompt = messages[-1]["content"] if isinstance(messages[-1], dict) else str(messages[-1]) if kwargs.get("profile_name") == "transaction_parameter_extraction" or "pending_parameters:" in prompt: return json.dumps({"subject": None, "valor": None}) + if "nao quero cancelar. quero apenas verificar os meus servicos contratados" in prompt.lower(): + return json.dumps({ + "decision": "ABANDON", + "intent": "product_services_information", + "agent": "product_agent", + "confidence": 0.99, + "reason": "abandono explícito do cancelamento com novo objetivo", + }) if "nao quero mais cancelar" in prompt.lower() or "não quero mais cancelar" in prompt.lower(): return json.dumps({ "decision": "ABANDON", @@ -48,6 +56,8 @@ router: state_policies: - state: COLLECTING_PARAMETERS agent: contestacao_agent + - state: WAITING_CONTESTACAO_CONFIRMATION + agent: contestacao_agent intents: - name: contas_contestation agent: contestacao_agent @@ -61,6 +71,10 @@ intents: agent: billing_agent priority: 30 keywords: [fatura] + - name: product_services_information + agent: product_agent + priority: 40 + keywords: [servicos contratados, serviços contratados] """, encoding="utf-8", ) @@ -123,6 +137,87 @@ async def test_explicit_abandonment_without_new_goal_does_not_rearm_tool(tmp_pat assert decision.metadata["interrupted_intent"] == "contas_contestation" + + +def _awaiting_confirmation_state(message): + return { + "user_text": message, + "sanitized_input": message, + "next_state": "WAITING_CONTESTACAO_CONFIRMATION", + "transaction_status": "AWAITING_CONFIRMATION", + "intent": "state:WAITING_CONTESTACAO_CONFIRMATION", + "active_agent": "contestacao_agent", + "active_transaction": { + "tool_name": "cancelar_vas_avulso", + "arguments": {"subject": "Tamboro Mensal"}, + "status": "AWAITING_CONFIRMATION", + "started_from_intent": "contas_vas_cancel", + }, + "pending_tool_call": { + "tool_name": "cancelar_vas_avulso", + "arguments": {"subject": "Tamboro Mensal"}, + }, + "history": [ + {"role": "user", "content": "quero cancelar tamboro"}, + {"role": "assistant", "content": "Você confirma o cancelamento?"}, + {"role": "user", "content": message}, + ], + } + + +@pytest.mark.asyncio +async def test_explicit_abandonment_precedes_awaiting_confirmation_and_routes_new_goal(tmp_path): + decision = await _router(tmp_path).route( + _awaiting_confirmation_state( + "nao quero cancelar. quero apenas verificar os meus servicos contratados" + ) + ) + assert decision.intent == "product_services_information" + assert decision.agent == "product_agent" + assert decision.metadata["transaction_interruption"] == "explicit_abandonment" + assert decision.metadata["interrupted_intent"] == "contas_vas_cancel" + assert not decision.metadata.get("transaction_turn_consumed") + + +@pytest.mark.asyncio +async def test_plain_negative_confirmation_remains_confirmation_not_abandon(tmp_path): + decision = await _router(tmp_path).route(_awaiting_confirmation_state("não")) + assert decision.intent == "state:WAITING_CONTESTACAO_CONFIRMATION" + assert decision.agent == "contestacao_agent" + assert decision.metadata["transaction_turn_consumed"] is True + assert decision.metadata["transaction_confirmation_decision"] == "reject" + assert "transaction_interruption" not in decision.metadata + +class _MisleadingParameterRouterLLM(_RouterLLM): + async def ainvoke(self, messages, **kwargs): + prompt = messages[-1]["content"] if isinstance(messages[-1], dict) else str(messages[-1]) + # Simulate the real failure mode: the parameter extractor hallucinates a + # usable value from an abandonment utterance. ABANDON must win before + # parameter extraction is allowed to consume the turn. + if kwargs.get("profile_name") == "transaction_parameter_extraction" or "pending_parameters:" in prompt: + if "nao quero mais cancelar" in prompt.lower() or "não quero mais cancelar" in prompt.lower(): + return json.dumps({"valor": 14.99}) + return await super().ainvoke(messages, **kwargs) + + +def _router_with_misleading_parameter_extractor(tmp_path): + router = _router(tmp_path) + router.llm = _MisleadingParameterRouterLLM() + return router + + +@pytest.mark.asyncio +async def test_explicit_abandonment_precedes_misleading_parameter_extraction(tmp_path): + decision = await _router_with_misleading_parameter_extractor(tmp_path).route( + _active_state("esquece, nao quero mais cancelar") + ) + assert decision.intent == "state:TRANSACTION_ABANDONED" + assert decision.agent == "contestacao_agent" + assert decision.mcp_tools == [] + assert decision.metadata["transaction_interruption"] == "explicit_abandonment" + assert not decision.metadata.get("transaction_turn_consumed") + + class _PolicyRouter: def __init__(self): self.registry = SimpleNamespace( diff --git a/agent_framework_oci/tests/unit/__pycache__/test_pending_topics_isolation.cpython-313-pytest-9.0.2.pyc b/agent_framework_oci/tests/unit/__pycache__/test_pending_topics_isolation.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 00000000..aadd2a8f Binary files /dev/null and b/agent_framework_oci/tests/unit/__pycache__/test_pending_topics_isolation.cpython-313-pytest-9.0.2.pyc differ