Ajustes conforme relatorio de testes 2026-08-27

This commit is contained in:
2026-08-29 10:03:42 -03:00
parent 1fd18531c0
commit 81f24d7357
699 changed files with 7989 additions and 2364 deletions

View File

@@ -557,3 +557,82 @@ The files below were consolidated into this manual:
### Maintenance rule
New fixes or evolutions for this subject should update this consolidated document. Release notes may continue to exist as history, but they should not be required to understand or implement the feature.
## Canonical resolution and domain revalidation before execution
When pre-validation resolves a user reference to a canonical entity, the framework **must not blindly overwrite the parameter and execute the originally selected tool**. The contract keeps requested, resolved and execution values distinct.
A domain validator may return `transaction_decision` with `resolved_arguments`, `target_tool`, `action_changed`, `requires_reconfirmation`, and an optional customer-facing `confirmation_message`.
Responsibilities:
- **Framework:** preserve the requested arguments, apply only canonical arguments declared by the validator, update the transaction to the effective `target_tool`, honor reconfirmation, and retain the decision in pre-validation evidence.
- **Agent/domain:** decide business class, policy and effective tool. The framework must not know rules such as “Youtube Premium is strategic”.
- **MCP/backend:** execute the final operation chosen by the domain.
If canonicalization does not change the action, the current tool may remain valid. If entity resolution changes business class/policy/tool, domain revalidation must happen **before confirmation and execution**. Ambiguous or low-confidence resolution must request clarification instead of silently promoting a candidate.
### Troubleshooting: resolved_subject is correct but execution receives the original text
If pre-validation records `resolved_subject="Youtube Premium"` while execution still receives `subject="youtube"`, verify that the validator returns `transaction_decision.resolved_arguments` and that the runtime applies the decision before freezing `pending_tool_call` / `confirmation_snapshot`. If the canonical entity is correct but the final tool is wrong, inspect `transaction_decision.target_tool`; that business reclassification belongs to the domain validator, not to the framework.
For domains that expose an authoritative business classification in backend detail, revalidation should use that evidence before aggregated categories. In Contas, for example, `invoice_detail.parsed_content` preserves `classe=avulso|estrategico|bundle`, while `billing_analysis` may group the same item into broader sections such as `streaming` or partner services. Canonical entity discovery may use any authorized evidence, but the **business decision** should prioritize the source that preserves the domain classification. If classification evidence conflicts, do not silently change the action; preserve the current operation or request clarification according to the agent policy.
## Semantic transactional confirmation: SIM / NAO / CONTINUAR
Transactions in `AWAITING_CONFIRMATION` use two layers, in this order:
1. **Deterministic parser** for explicit confirmations/rejections (`sim`, `não`, `confirmo`, `pode fazer`, etc.). This remains the cheapest and safest path and **does not call an LLM**.
2. **LLM semantic fallback** only when the deterministic parser is inconclusive. The fallback reuses the same declarative semantic-classifier engine used by paused workflow `expected_input`, injecting the pending prompt, recent context related to the same topic, and the current user utterance.
Configuration lives in `config/routing.yaml` under `router.transaction_confirmation.semantic_fallback`:
```yaml
router:
transaction_confirmation:
semantic_fallback:
enabled: true
allowed_values: [SIM, NAO, CONTINUAR]
confirm_values: [SIM]
reject_values: [NAO]
continue_values: [CONTINUAR]
include_relevant_context: true
profile_name: router
prompt: |
Allowed classes: {{ allowed_values }}
Pending prompt:
{{ pending_prompt }}
Relevant context:
{{ relevant_conversation_context }}
Current user input:
{{ user_input }}
```
`SIM` means an unambiguous acceptance, `NAO` an unambiguous rejection, and `CONTINUAR` means the utterance does not safely confirm or reject the pending action. Example: after `Você confirma o cancelamento do serviço Tamboro Mensal?`, the reply `isso mesmo, pode confirmar` can be classified as `SIM` without hardcoding that exact sentence.
When semantic confirmation succeeds, the router records:
```json
{
"transaction_turn_consumed": true,
"transaction_confirmation_decision": "confirm",
"transaction_confirmation_source": "semantic"
}
```
`AgentRuntime` reuses this routed decision instead of re-running the deterministic parser. The change is additive: existing explicit yes/no inputs continue through the deterministic path with no extra LLM call. Semantic generations are named `transaction.confirmation.semantic_classifier` for observability.
### Durable interrupt compatibility in pause/resume
The runtime does not use `snapshot.next` alone to decide whether a workflow is paused. A truthy `next` may represent LangGraph helper work, including framework-generated synthetic nodes such as `__pause` and `__continue`.
A pause is recognized only from a real interrupt. Depending on the LangGraph/checkpointer version, that interrupt may be exposed through `task.interrupts` or persisted in `snapshot.values["__interrupt__"]`. The runtime supports both shapes and deduplicates the payload when both are present.
This prevents two false diagnoses:
- treating `snapshot.next` as `PAUSED` when no real interrupt exists;
- treating `next=("<node>__pause",)` as invalid pending work when the real interrupt is persisted under `__interrupt__`.
For workflows using `expected_input.semantic_classifier`, internal tokens such as `SIM`, `NAO`, and `CONTINUAR` remain resume control values and must not be confused with customer-facing output.

View File

@@ -57,6 +57,69 @@ This version adds a pragmatic guardrail layer to `agent_framework`, inspired by
- `RET_REL` — validates retrieval-chunk relevance using a minimum score.
- `TOOL_VAL` — validates MCP/tool name, required arguments, negative values, and allowlist.
### Contract for authorized protocols in output guardrails
When a workflow or tool produces a **protocol/reference number that must be shown to the same customer**, the agent integration code must register that value in the output context before output guardrails run:
```python
ctx["expected_protocols"] = [protocol_number]
```
This field is a **framework contract**. It declares that those exact values were produced or validated by the current flow and may therefore be used by output guardrails as authorization evidence.
Expected flow:
```text
workflow/tool produces protocol
agent registers it in expected_protocols
CMP validates that the displayed protocol belongs to the expected values
DLEX_OUT does not block that protocol merely because it is an identifier
response may disclose the protocol to the customer
```
Important rules:
- `expected_protocols` must contain **only protocols actually produced/expected in the current turn or transaction**.
- Do not use `expected_protocols` to allow tokens, credentials, arbitrary internal IDs, or third-party data.
- Authorization applies only to listed values; any other identifier remains subject to normal `DLEX_OUT` rules.
- The value must be propagated **before `output_guardrails`**. Adding it later has no effect.
- For transactional responses, keep protocol evidence in the tool/workflow result so `CMP`, `GND`, and observability can correlate the value.
Example:
```python
result = await execute_workflow(...)
protocol_number = result.get("protocol_number") or result.get("protocolo_id")
if protocol_number:
ctx["expected_protocols"] = [str(protocol_number)]
```
#### Troubleshooting: workflow completed but the response was replaced by a safety message
Typical symptom:
```text
workflow = COMPLETED
CMP = allowed
DLEX_OUT = blocked because of "internal protocol"
final response = "I could not safely validate this response..."
```
Check, in this order:
1. Is the generated protocol present in the tool/workflow result or evidence?
2. Did the agent propagate the same value in `ctx["expected_protocols"]`?
3. Was `expected_protocols` populated before `output_guardrails`?
4. Is the protocol shown in the response exactly one of the expected values?
5. Is `DLEX_OUT` actually blocking another real issue such as a secret, token, or third-party data?
If `expected_protocols` is absent, the framework must not assume that an arbitrary textual identifier is safe to disclose.
### Files changed
- `agent_framework/src/agent_framework/guardrails/rails.py`

View File

@@ -0,0 +1,157 @@
# 12 — Input Guardrail Feedback and Blocked-Turn Semantics
## Goal
This document describes how `AgentWorkflow`, implemented in `app/workflows/agent_graph.py`, should handle a turn interrupted by an input guardrail without turning every interruption into a generic “security rule” message.
The core rule is to keep three concerns separate:
1. **the guardrail technical decision**, used by the runtime and observability;
2. **the user-facing message**, appropriate to the type of block or clarification need;
3. **the turn state**, which must not carry routing, tool, or judge data from a turn that was interrupted before those stages.
## Expected flow
```text
user message
input_guardrails
allowed?
├─ yes → routing → tools/agent → composition → output_guardrails
└─ no
select public handling
clear routing/tools/judges state for this turn
build a safe user-facing message
output_guardrails
persistence/response
```
A blocking input guardrail must be decided **before any side-effecting tool is executed**.
## Internal `reason` is not the user response
The `reason` field should remain available to logs, traces, events, and diagnostics. It should not be exposed verbatim when it may reveal internal mechanisms or when the technical wording is not appropriate for the end user.
Example:
```text
COER.reason = "utterance is incomprehensible or contains an ambiguous negation"
```
A public response may be:
```text
"I could not fully understand your last message because it seems incomplete or ambiguous. Could you rephrase or complete what you meant?"
```
## Handling by guardrail type
Exact behavior remains configurable, but the expected semantics are:
| Guardrail | Recommended public handling |
|---|---|
| `COER` | ask for clarification/rephrasing; do not frame ordinary ambiguity as a security incident |
| `PINJ` | block safely without describing the internal mechanism |
| `DLEX_IN` | block or request reformulation without exposing internal/sensitive data |
| `INPUT_SIZE` | ask the user to reduce the input |
| `TOX` | apply the configured policy for inappropriate content |
| `CMP` | respond according to the compliance policy |
| unknown | use a safe generic fallback |
## Clearing blocked-turn state
When input is blocked before routing, the final state for that turn must not reuse residual data from the previous turn.
At a minimum, the workflow should avoid presenting these as current:
```text
route_decision
mcp_tools
mcp_results
judge_results
```
Metadata should clearly indicate that the turn was interrupted at the input-guardrail stage.
This prevents misleading diagnostics such as:
```text
route = blocked
mcp_results = [tool executed]
```
when the tool result actually belongs to the previous turn.
## The public message also goes through output guardrails
A response created because of an input block is still agent output. Therefore it should follow the same output-validation pipeline before reaching the user.
This allows `DLEX_OUT`, `PINJ`, `TOXOUT`, Output Supervisor, and other policies to remove or sanitize information that should not be exposed.
## Relationship with `agent_graph.py`
This feature belongs to template orchestration because it defines precedence between graph nodes and blocked-turn state semantics.
When changing `app/workflows/agent_graph.py`, preserve these invariants:
- `input_guardrails` runs before routing/tools;
- an input block does not execute a transactional action after the block;
- the public response is not the raw guardrail `reason`;
- residual routing/tools/judges state does not survive as the blocked turn result;
- the public message passes through `output_guardrails` before persistence/response.
The same semantics must be preserved in the official templates and equivalent variants under `Tuning-Performance`.
## Troubleshooting
### The user receives “I could not continue because of a security rule” for a merely incomplete phrase
Check:
1. which guardrail returned `allowed=false`;
2. whether `COER` is handled as clarification rather than a generic security block;
3. whether the blocked branch builds a guardrail-specific public message;
4. whether the generic fallback is used only when no specific handling exists.
### Metadata shows a tool as executed while `route=blocked`
Check whether the blocked branch clears transient turn state before returning. Also confirm that the tool was not executed in the same turn before input-guardrail evaluation.
### The block response exposes internal details
Do not use `reason` directly as user-facing text. Generate the public message and keep `reason` for observability only.
### The block response skips output guardrails
Check the graph edge. The expected path is:
```text
input_guardrails blocked
→ build public response
→ output_guardrails
→ persist
```
not:
```text
input_guardrails blocked
→ persist
```
## Recommended regression tests
Cover at least:
- `COER=false` asks for clarification instead of returning a generic security message;
- blocked branch does not retain previous-turn `mcp_results`/routing;
- no transactional tool executes after an input block;
- the public message passes through output guardrails;
- an unknown guardrail still has a safe generic fallback.

View File

@@ -6,7 +6,7 @@ The documentation has three clear levels:
1. **Main tutorial:** [`README_en.md`](README_en.md) — creation, configuration, execution, and testing of an agent from start to finish.
2. **Architecture:** [01 — Architecture and Concepts](docs/developer/en/01_architecture_and_concepts.md) — components, responsibilities, and where to implement each concern.
3. **Specialized references:** manuals `02` through `11` — in-depth implementation and troubleshooting by capability.
3. **Specialized references:** manuals `02` through `12` — in-depth implementation and troubleshooting by capability.
If you are starting a new agent, begin with `README_en.md`.
@@ -31,7 +31,10 @@ If something is not working, use **Search by problem** below.
| I receive 401 between gateway/backend/MCP | Basic Auth, credentials per hop | [Gateways and Auth](docs/developer/en/05_agent_gateway_mcp_gateway_and_auth.md) |
| I need to decide whether something belongs to the framework or the agent | core/agent boundary | [Architecture and Concepts](docs/developer/en/01_architecture_and_concepts.md) |
| An agent-specific guardrail is breaking another agent | extensibility, domain imports in the core | [Guardrails and Judges](docs/developer/en/06_guardrails_judges_and_transaction_evaluation.md) |
| An incomplete phrase receives a generic “security rule” message | input-guardrail feedback, `COER`, blocked-turn state | [Input Guardrail Feedback](./12_input_guardrail_feedback_and_blocked_turns.md) |
| `route=blocked` appears together with tools/results from another turn | blocked-turn state cleanup | [Input Guardrail Feedback](./12_input_guardrail_feedback_and_blocked_turns.md) |
| A judge does not run in a transaction | sampling, `always_run_for_transactional`, transaction signals | [Guardrails and Judges](docs/developer/en/06_guardrails_judges_and_transaction_evaluation.md) |
| Workflow completes and generates a protocol, but the final response becomes a safety message | `expected_protocols`, `CMP`, `DLEX_OUT`, `output_guardrails` ordering | [Guardrails and Judges](./06_guardrails_judges_and_transaction_evaluation.md) |
| Groundedness is evaluating without the correct context | RAG context, MCP evidence, judge inputs | [RAG/Grounding](docs/developer/en/07_rag_business_context_and_grounding.md) |
| RAG does not find content | provider, ingestion, embeddings, configuration | [RAG/Grounding](docs/developer/en/07_rag_business_context_and_grounding.md) |
| I do not know whether to use RAG, memory, or a tool | separation of responsibilities | [Architecture and Concepts](docs/developer/en/01_architecture_and_concepts.md) and [RAG/Grounding](docs/developer/en/07_rag_business_context_and_grounding.md) |
@@ -115,6 +118,12 @@ If something is not working, use **Search by problem** below.
**Use when:** it is necessary to prove the executed path or diagnose production.
### [12 — Input Guardrail Feedback and Blocked-Turn Semantics](./12_input_guardrail_feedback_and_blocked_turns.md)
**What it is:** public handling of input blocks, blocked-turn state cleanup, and output-guardrail validation of generated feedback.
**Use when:** block messages are generic, `COER` should ask for clarification, or blocked-turn metadata contains stale routing/tool results.
### Main tutorial
[`README_en.md`](README_en.md) remains the reference for the complete step-by-step flow:
@@ -131,3 +140,4 @@ When evolving a feature:
- update the specialized manual with behavior, configuration, examples, and troubleshooting;
- update SPECs if the contract changed;
- keep release notes as history, not as the only current documentation.

View File

@@ -558,3 +558,136 @@ Os arquivos abaixo foram consolidados neste manual:
### Regra de manutenção
Novas correções ou evoluções deste tema devem atualizar este documento consolidado. Release notes podem continuar existindo como histórico, mas não devem ser necessárias para compreender ou implementar a funcionalidade.
## Resolução canônica e revalidação de domínio antes da execução
Quando uma pré-validação resolve uma referência do usuário para uma entidade canônica, o framework **não deve simplesmente sobrescrever o parâmetro e executar a tool originalmente escolhida**. O contrato separa três valores:
```text
requested_subject = "youtube"
resolved_subject = "Youtube Premium"
execution_subject = "Youtube Premium"
```
O validador de domínio pode devolver `transaction_decision` com:
```json
{
"resolved_arguments": {"subject": "Youtube Premium"},
"target_tool": "tratar_vas_estrategico",
"action_changed": true,
"requires_reconfirmation": true,
"confirmation_message": "Identifiquei o serviço Youtube Premium. Esse serviço possui tratamento específico. Você deseja prosseguir?"
}
```
Responsabilidades:
- **Framework:** preserva argumentos solicitados, aplica apenas os argumentos canônicos declarados pelo validador, atualiza a transação para a `target_tool`, respeita `requires_reconfirmation` e mantém a decisão na evidência de pré-validação.
- **Agente/domínio:** decide classe, política e tool efetiva. O framework não conhece regras como “Youtube Premium é estratégico”.
- **MCP/backend:** executa a operação final já decidida pelo domínio.
Se a canonicalização não alterar a ação (`Tamboro``Tamboro Mensal`, por exemplo), a tool pode permanecer a mesma. Se a resolução alterar classe/política/tool, a decisão de domínio precisa ocorrer **antes da confirmação e da execução**. Em caso de ambiguidade ou baixa confiança, o validador deve pedir nova coleta/clarificação em vez de promover silenciosamente um candidato.
### Troubleshooting: resolved_subject correto, mas tool recebe o texto original
Sintoma: a pré-validação registra `resolved_subject="Youtube Premium"`, porém a execução ainda recebe `subject="youtube"`. Verifique se o validador retorna `transaction_decision.resolved_arguments` e se o runtime aplicou a decisão antes de congelar `pending_tool_call`/`confirmation_snapshot`.
Sintoma: a entidade foi resolvida corretamente, mas a tool final continua inadequada. Verifique `transaction_decision.target_tool`; a reclassificação de domínio pertence ao agente/validador, não ao framework.
Para domínios que possuem uma classificação autoritativa no detalhe do backend, a revalidação deve usar essa evidência antes de categorias agregadas. No Contas, por exemplo, `invoice_detail.parsed_content` preserva `classe=avulso|estrategico|bundle`; `billing_analysis` pode agrupar o mesmo item em seções mais amplas como `streaming` ou serviços de parceiros. A entidade canônica pode ser descoberta por qualquer evidência autorizada, mas a **decisão de negócio** deve priorizar a fonte que preserva a classificação de domínio. Se houver conflito de classificação, não troque a ação silenciosamente: mantenha a operação original ou peça esclarecimento conforme a política do agente.
## Confirmação transacional semântica: SIM / NAO / CONTINUAR
Transações em `AWAITING_CONFIRMATION` usam duas camadas, nesta ordem:
1. **Parser determinístico** para confirmações/recusas explícitas (`sim`, `não`, `confirmo`, `pode fazer`, etc.). Esse caminho continua sendo o mais barato, rápido e seguro e **não chama LLM**.
2. **Fallback semântico por LLM** somente quando o parser determinístico retorna inconclusivo. O fallback reutiliza o mesmo mecanismo declarativo de `expected_input.semantic_classifier` dos workflows pausados e injeta a pergunta pendente, o histórico recente relacionado ao mesmo tema e a fala atual.
A configuração fica em `config/routing.yaml`, sob `router.transaction_confirmation.semantic_fallback`:
```yaml
router:
transaction_confirmation:
semantic_fallback:
enabled: true
allowed_values: [SIM, NAO, CONTINUAR]
confirm_values: [SIM]
reject_values: [NAO]
continue_values: [CONTINUAR]
include_relevant_context: true
profile_name: router
prompt: |
Classes permitidas: {{ allowed_values }}
Pergunta pendente:
{{ pending_prompt }}
Histórico relevante:
{{ relevant_conversation_context }}
Resposta atual:
{{ user_input }}
```
### Significado das classes
- `SIM`: aceite inequívoco da ação pendente. Exemplos: `isso mesmo, pode confirmar`, `é isso`, `pode seguir`, quando o contexto torna o aceite claro.
- `NAO`: recusa inequívoca da ação pendente. Exemplos: `melhor não`, `não quero mais`, `cancela isso`.
- `CONTINUAR`: a fala não confirma nem rejeita de forma inequívoca. Exemplos: pergunta adicional, correção de parâmetro, informação nova, ambiguidade ou possível mudança de assunto. Nesse caso a tool não é executada por confirmação.
### Exemplo
Contexto:
```text
Cliente: quero cancelar o Tamboro Mensal
Agente: Você confirma o cancelamento do serviço Tamboro Mensal?
Cliente: isso mesmo, pode confirmar
```
O parser determinístico não precisa conhecer literalmente `isso mesmo, pode confirmar`. O fallback recebe:
```text
pending_prompt = "Você confirma o cancelamento do serviço Tamboro Mensal?"
relevant_conversation_context = histórico recente do mesmo fluxo
user_input = "isso mesmo, pode confirmar"
```
e deve retornar apenas:
```text
SIM
```
O router então publica em `route_decision.metadata`:
```json
{
"transaction_turn_consumed": true,
"transaction_confirmation_decision": "confirm",
"transaction_confirmation_source": "semantic"
}
```
O `AgentRuntime` reutiliza essa decisão e **não tenta reclassificar a mesma fala com o parser determinístico**. Isso evita a regressão em que o router entende semanticamente a confirmação, mas o runtime volta a tratá-la como inconclusiva.
### Precedência e compatibilidade
A funcionalidade é aditiva. Entradas determinísticas já suportadas continuam com o mesmo comportamento e sem custo adicional de LLM. O fallback semântico só roda quando a primeira camada não consegue decidir. Assim, `sim` e `não` continuam tendo precedência absoluta sobre `intent_shift`. Uma saída `CONTINUAR` não confirma nem rejeita automaticamente a transação; o fluxo normal pode então avaliar continuação contextual ou mudança de intenção conforme as políticas existentes.
### Observabilidade
Para confirmações semânticas, o framework registra a geração como `transaction.confirmation.semantic_classifier` e acrescenta ao metadata do roteamento a fonte `semantic`, a classificação retornada e o contexto conversacional relevante utilizado. Para confirmações literais, a fonte permanece `deterministic`.
### Compatibilidade de interrupts duráveis no pause/resume
O runtime não usa `snapshot.next` isoladamente para decidir se um workflow está pausado. Um `next` pode representar trabalho auxiliar do LangGraph, inclusive nós sintéticos criados pelo framework como `__pause` e `__continue`.
A pausa é reconhecida por um interrupt real. Dependendo da versão do LangGraph/checkpointer, esse interrupt pode aparecer em `task.interrupts` ou persistido em `snapshot.values["__interrupt__"]`. O runtime aceita ambas as formas e deduplica o payload quando as duas são expostas simultaneamente.
Isso evita dois falsos diagnósticos:
- considerar `snapshot.next` como `PAUSED` quando não existe interrupt real;
- considerar um `next=("<node>__pause",)` como erro de trabalho pendente quando o interrupt está persistido em `__interrupt__`.
Em workflows com `expected_input.semantic_classifier`, os tokens internos `SIM`, `NAO` e `CONTINUAR` continuam sendo valores de controle do resume e não devem ser confundidos com resposta final ao cliente.

View File

@@ -58,6 +58,69 @@ Esta versão adiciona uma camada pragmática de guardrails ao `agent_framework`,
- `RET_REL` — valida relevância de chunks de retrieval por score mínimo.
- `TOOL_VAL` — valida ferramenta MCP/tool, argumentos obrigatórios, valores negativos e allowlist.
### Contrato para protocolos autorizados em guardrails de saída
Quando um workflow ou tool produz um **protocolo que deve ser exibido ao próprio cliente**, o código de integração do agente deve registrar esse valor no contexto de saída antes da execução dos guardrails:
```python
ctx["expected_protocols"] = [protocol_number]
```
Esse campo é um **contrato do framework**. Ele informa que aqueles valores específicos foram produzidos ou validados pelo fluxo atual e, portanto, podem ser usados pelos guardrails de saída como evidência de autorização.
Fluxo esperado:
```text
workflow/tool gera protocolo
agente registra em expected_protocols
CMP valida que o protocolo exibido pertence aos valores esperados
DLEX_OUT não bloqueia esse protocolo apenas por classificá-lo como identificador
resposta pode informar o protocolo ao cliente
```
Regras importantes:
- `expected_protocols` deve conter **somente protocolos realmente produzidos/esperados no turno ou transação atual**.
- Não use `expected_protocols` para liberar tokens, credenciais, IDs internos arbitrários ou dados de terceiros.
- A autorização vale somente para os valores listados; outro identificador continua sujeito às regras normais de `DLEX_OUT`.
- O valor deve ser propagado **antes de `output_guardrails`**. Se o protocolo só for adicionado depois, a autorização não terá efeito.
- Em respostas transacionais, mantenha a evidência do protocolo no resultado da tool/workflow para que `CMP`, `GND` e observabilidade consigam correlacionar o valor.
Exemplo:
```python
result = await executar_workflow(...)
protocol_number = result.get("protocol_number") or result.get("protocolo_id")
if protocol_number:
ctx["expected_protocols"] = [str(protocol_number)]
```
#### Troubleshooting: workflow concluiu, mas a resposta foi substituída por mensagem de segurança
Sintoma típico:
```text
workflow = COMPLETED
CMP = allowed
DLEX_OUT = blocked por "protocolo interno"
resposta final = "Não consegui validar essa resposta com segurança..."
```
Verifique, nesta ordem:
1. O protocolo gerado está presente no resultado/evidência da tool ou workflow?
2. O agente propagou o mesmo valor em `ctx["expected_protocols"]`?
3. `expected_protocols` foi preenchido antes de `output_guardrails`?
4. O protocolo presente na resposta é exatamente um dos valores esperados?
5. O `DLEX_OUT` está bloqueando por outro motivo real, como segredo, token ou dado de terceiro?
Se `expected_protocols` estiver ausente, o framework não deve presumir que qualquer identificador textual é seguro para divulgação.
### Arquivos alterados
- `agent_framework/src/agent_framework/guardrails/rails.py`

View File

@@ -0,0 +1,157 @@
# 12 — Feedback de Guardrails de Entrada e Semântica de Turno Bloqueado
## Objetivo
Este documento descreve como o `AgentWorkflow`, implementado em `app/workflows/agent_graph.py`, deve tratar um turno interrompido por guardrail de entrada sem transformar toda interrupção em uma mensagem genérica de “regra de segurança”.
A regra central é separar três coisas:
1. **decisão técnica do guardrail**, usada pelo runtime e pela observabilidade;
2. **mensagem pública ao usuário**, adequada ao tipo de bloqueio ou necessidade de esclarecimento;
3. **estado do turno**, que não pode carregar routing, tools ou judges de um turno que foi interrompido antes dessas etapas.
## Fluxo esperado
```text
mensagem do usuário
input_guardrails
allowed?
├─ sim → routing → tools/agente → composição → output_guardrails
└─ não
classificar tratamento público
limpar estado de routing/tools/judges do turno
construir mensagem pública segura
output_guardrails
persistência/resposta
```
Um guardrail de entrada bloqueante deve ser decidido **antes de qualquer tool com efeito colateral**.
## `reason` interno não é a resposta ao usuário
O campo `reason` deve permanecer disponível para logs, traces, eventos e diagnóstico. Ele não deve ser exibido literalmente quando puder revelar mecanismo interno ou quando a frase técnica não for apropriada ao usuário final.
Exemplo:
```text
COER.reason = "fala incompreensível ou negação ambígua na transcrição"
```
A resposta pública pode ser:
```text
"Não consegui entender sua última mensagem porque ela parece incompleta ou ambígua. Pode reformular ou completar o que você quis dizer?"
```
## Tratamento por tipo de guardrail
O comportamento exato continua configurável, mas a semântica esperada é:
| Guardrail | Tratamento público recomendado |
|---|---|
| `COER` | solicitar esclarecimento/reformulação; não tratar ambiguidade como incidente de segurança |
| `PINJ` | bloquear com mensagem segura sem explicar o mecanismo interno |
| `DLEX_IN` | bloquear ou orientar reformulação sem expor dado interno/sensível |
| `INPUT_SIZE` | solicitar redução da entrada |
| `TOX` | aplicar a política configurada para conteúdo inadequado |
| `CMP` | responder segundo a política de compliance |
| desconhecido | usar fallback seguro e genérico |
## Limpeza do estado do turno bloqueado
Quando o input é bloqueado antes do routing, o estado final daquele turno não deve reutilizar dados residuais do turno anterior.
No mínimo, o workflow deve evitar apresentar como atuais:
```text
route_decision
mcp_tools
mcp_results
judge_results
```
O metadata deve deixar explícito que o turno foi interrompido no estágio de input guardrails.
Isso evita um diagnóstico falso como:
```text
route = blocked
mcp_results = [tool executada]
```
quando a tool na realidade pertence ao turno anterior.
## Mensagem pública também passa pelos guardrails de saída
Uma resposta criada em função de um bloqueio de entrada ainda é uma saída do agente. Portanto ela deve seguir o mesmo pipeline de validação de saída antes de chegar ao usuário.
Isso permite que `DLEX_OUT`, `PINJ`, `TOXOUT`, Output Supervisor e outras políticas removam ou sanitizem informação que não deva ser apresentada.
## Relação com `agent_graph.py`
Esta feature é responsabilidade da orquestração do template, porque define a precedência entre nós do grafo e o estado do turno.
Ao alterar `app/workflows/agent_graph.py`, preserve estas invariantes:
- `input_guardrails` antecede routing/tools;
- um bloqueio de input não executa ação transacional depois do bloqueio;
- resposta pública não é o `reason` bruto do guardrail;
- estado residual de routing/tools/judges não sobrevive como resultado do turno bloqueado;
- a resposta pública passa por `output_guardrails` antes da persistência/resposta.
A mesma semântica deve ser mantida nos templates oficiais e nas variantes equivalentes em `Tuning-Performance`.
## Troubleshooting
### O usuário recebe “Não consegui seguir com essa mensagem por regra de segurança” para uma frase apenas incompleta
Verifique:
1. qual guardrail retornou `allowed=false`;
2. se `COER` está sendo tratado como esclarecimento e não como bloqueio genérico;
3. se o caminho de bloqueio usa uma mensagem pública específica;
4. se o fallback genérico está sendo usado somente quando não existe tratamento específico.
### O metadata mostra tool executada mesmo com `route=blocked`
Verifique se o ramo de bloqueio limpa o estado transitório do turno antes de retornar a resposta. Confirme também se a tool não foi executada no mesmo turno antes do guardrail de entrada.
### A mensagem de bloqueio expõe detalhes internos
Não use `reason` diretamente como texto público. Gere a mensagem pública e deixe o `reason` apenas em observabilidade.
### A resposta de bloqueio ignora guardrails de saída
Verifique a aresta do grafo. O fluxo esperado é:
```text
input_guardrails bloqueou
→ construir resposta pública
→ output_guardrails
→ persist
```
não:
```text
input_guardrails bloqueou
→ persist
```
## Testes de regressão recomendados
Cubra pelo menos:
- `COER=false` gera solicitação de esclarecimento, não mensagem genérica de segurança;
- ramo bloqueado não conserva `mcp_results`/routing de turno anterior;
- nenhuma tool transacional é executada depois de um bloqueio de input;
- mensagem pública passa pelos guardrails de saída;
- guardrail desconhecido ainda possui fallback seguro.

View File

@@ -7,7 +7,7 @@ A documentação possui três níveis claros:
1. **Tutorial principal:** [`README.md`](../../../README.md) — criação, configuração, execução e teste de um agente do início ao fim.
2. **Arquitetura:** [01 — Arquitetura e Conceitos](./01_architecture_and_concepts.md) — componentes, responsabilidades e onde implementar cada coisa.
3. **Referências especializadas:** manuais `02` a `11` — implementação profunda e troubleshooting por capacidade.
3. **Referências especializadas:** manuais `02` a `12` — implementação profunda e troubleshooting por capacidade.
Se você está começando um novo agente, comece pelo `README.md`.
@@ -32,6 +32,9 @@ Se algo não está funcionando, use **Buscar pelo problema** abaixo.
| Recebo 401 entre gateway/backend/MCP | Basic Auth, credenciais por hop | [Gateways e Auth](./05_agent_gateway_mcp_gateway_and_auth.md) |
| Preciso decidir se algo pertence ao framework ou ao agente | boundary core/agente | [Arquitetura e Conceitos](./01_architecture_and_concepts.md) |
| Guardrail específico de um agente está quebrando outro | extensibilidade, imports de domínio no core | [Guardrails e Judges](./06_guardrails_judges_and_transaction_evaluation.md) |
| Uma frase incompleta recebe mensagem genérica de “regra de segurança” | feedback de input guardrail, `COER`, blocked-turn state | [Feedback de Guardrails de Entrada](./12_input_guardrail_feedback_and_blocked_turns.md) |
| `route=blocked` aparece junto com tools/resultados de outro turno | limpeza de estado do turno bloqueado | [Feedback de Guardrails de Entrada](./12_input_guardrail_feedback_and_blocked_turns.md) |
| Workflow conclui e gera protocolo, mas a resposta final vira mensagem de segurança | `expected_protocols`, `CMP`, `DLEX_OUT`, ordem de `output_guardrails` | [Guardrails e Judges](./06_guardrails_judges_and_transaction_evaluation.md) |
| Judge não roda em uma transação | sampling, `always_run_for_transactional`, sinais transacionais | [Guardrails e Judges](./06_guardrails_judges_and_transaction_evaluation.md) |
| Groundedness está avaliando sem contexto correto | RAG context, MCP evidence, judge inputs | [RAG/Grounding](./07_rag_business_context_and_grounding.md) |
| RAG não encontra conteúdo | provider, ingestão, embeddings, configuração | [RAG/Grounding](./07_rag_business_context_and_grounding.md) |
@@ -116,6 +119,12 @@ Se algo não está funcionando, use **Buscar pelo problema** abaixo.
**Use quando:** for necessário provar o caminho executado ou diagnosticar produção.
### [12 — Feedback de Guardrails de Entrada e Turnos Bloqueados](./12_input_guardrail_feedback_and_blocked_turns.md)
**O que é:** tratamento público de bloqueios de input, limpeza do estado do turno e validação da mensagem gerada pelos guardrails de saída.
**Use quando:** mensagens de bloqueio são genéricas, `COER` deveria pedir esclarecimento ou o metadata de um turno bloqueado contém routing/tools antigos.
### Tutorial principal
[`README.md`](../../../README.md) continua sendo a referência para o passo a passo completo: