Ir para o conteúdo

45 · Protegido e provado

Idiomas

O seletor lê turnos escritos em inglês e em português do Brasil (pt-BR). O idioma é escolhido, nunca adivinhado a partir do turno: na página Settings do console (Language the selector reads), em language numa requisição de plano, ou no settings.language do seu arquivo de vocabulário; sem nenhum deles, inglês. Um turno em português pode ser digitado com os acentos ou sem eles. O português europeu não é uma gramática que o seletor tenha, e um turno em qualquer outra língua não é lido.

Um agente do stalwart-agents é protegido enquanto roda e provado antes de rodar. Duas verificações cercam o seu modelo, ambas decididas por código nativo na sua máquina -- o runtime licenciado que o pacote traz -- e nunca por um modelo: o roteador decide se o modelo é chamado, e a verificação de ferramentas decide cada chamada. Antes do primeiro turno do agente, o caçador de bugs examina o seu grafo em busca de um caminho que passe por fora de qualquer uma das duas. Este capítulo é como as duas verificações decidem, o que isso garante e como a prova roda.

Duas verificações e uma prova

  1. O roteador lê cada turno antes que qualquer modelo o veja (abaixo).
  2. A verificação de ferramentas decide cada chamada de ferramenta, tenha sido escolhida pelo roteador ou pelo modelo (abaixo).
  3. A prova examina cada rota pelos seus agentes em busca de um caminho até uma ferramenta sem a sua verificação, a sua confirmação ou a etapa que ela precisa seguir (abaixo).

A API hospedada não decide nada enquanto o seu agente roda. Onde o runtime não consegue responder, um plano ou uma verificação levanta SelectorUnavailableError e a chamada não é feita: o agente nunca recorre a uma chamada não verificada.

O roteador: se um modelo é chamado

Cada turno é lido num plano a partir do que as suas ferramentas declaram. Para onde o turno vai, gerado a partir do próprio roteador:

O que o roteador leu Para onde o turno vai
uma etapa de ferramenta que se resolve direto para a etapa de ferramenta protegida, com a chamada escrita a partir das palavras da pessoa: nenhuma chamada ao modelo
uma pergunta cujos valores que faltam as ferramentas de listagem do catálogo produzem (chain) para a etapa de ferramenta protegida com essas chamadas, uma rodada de cada vez, e depois o plano que os resultados delas fazem, no lugar da pergunta
uma pergunta o turno termina com uma pergunta fixa, e o modelo não é chamado
uma escrita submetida à pessoa (confirm) para a etapa de ferramenta protegida com a chamada que o sim da pessoa faria; a confirmação dela é a única pergunta feita
recusado (declined) o turno termina com uma recusa que nomeia a regra; o modelo nunca é visitado
um cálculo depois que as leituras de que depende rodaram, o próprio modelo do agente escreve a função e o sandbox a executa, na sua máquina
um cálculo que esta máquina não consegue rodar recusado, com o motivo começando por compute off:
não lido (unread) primeiro o único pedido para dizer de novo; depois o que on_unread diz

Um turno que nada lê recebe uma vez o pedido para ser dito de novo. Depois disso, o on_unread do agente decide:

Configuração O que acontece com um turno que nada lê
refuse o turno termina com uma recusa; nenhum modelo é chamado
model o turno vai para o modelo do agente, e toda chamada que ele pede é verificada
ask a pessoa é perguntada se o modelo pode receber o turno, e ele só vai para o modelo com o sim dela
answer uma pergunta é respondida a partir dos seus documentos (capítulo 44); sem resposta fundamentada, o turno é recusado

Onde um agente não diz nada, cada conversa adota no seu primeiro turno a configuração da página Configurações do seu console, ou refuse.

Quando nenhum modelo é chamado

O roteador responde aos turnos que lê sem modelo. Um modelo só é chamado onde as suas configurações enviam um turno para ele, então nenhum modelo é chamado quando tudo isto vale:

  • os agentes não nomeiam nenhum model;
  • on_unread é refuse, ou answer com o compositor extract (capítulo 44);
  • nenhum agente tem hands_off_to;
  • as respostas são as suas formulações: replies: {composer: template} (capítulo 42).

A verificação de ferramentas: cada chamada, quem quer que a tenha escolhido

A verificação roda no runtime, uma vez por chamada, com o catálogo, o rulebook e o histórico da sessão, e a primeira regra que se aplica decide. As regras, na ordem em que o runtime as toma (o capítulo 32 dá o exemplo de cada uma):

Ordem O que é verificado Motivos com que responde Exemplo
1 a ferramenta está no catálogo e este agente pode alcançá-la; com vários agentes, a chamada nomeia o seu agente; com a licença vencida, uma escrita é recusada, ou toda chamada quando a configuração do tenant diz refuse unknown_tool, agent_required, licence_lapsed delete_order, que o catálogo não tem: refuse, unknown_tool
2 o histórico da sessão se verifica, entrada por entrada, pela assinatura que cada entrada carrega sobre as anteriores history_invalid um histórico cuja primeira entrada foi editada à mão: refuse, history_invalid
3 os argumentos contra o schema da ferramenta; um argumento que o schema não declara é recusado argument_schema refund_card com um note que ela não declara: refuse, argument_schema em /note
4 um destino -- um endereço, uma URL, um host -- num argumento de uma ferramenta não declarada sends_out undeclared_destination refund_card com https://refunds.invalid/4411 como pedido: refuse, undeclared_destination
5 as regras de ordem da ferramenta sobre o histórico verificado: after (e um argumento ligado ao resultado daquela chamada), not_after, at_most order_rule uma ferramenta declarada after: [get_order], chamada antes de qualquer get_order rodar na sessão: refuse, order_rule, nomeando after get_order
6 um desfazer nomeia o passo que desfaz, esse passo rodou, o desfazer é o declarado para ele, cada argumento ligado vem desse passo, e o passo é desfeito uma única vez compensation_spent, compensation_mismatch refund_card enviado uma segunda vez como desfazer de uma cobrança já desfeita: refuse, compensation_spent
7 os checks da própria ferramenta sobre os argumentos da chamada e os resultados que ferramentas anteriores devolveram, os seus limites por período (at_most: {count, per}) e o que pode gastar (spend), contra o que o host contou; nunca num desfazer condition_failed, condition_unknown, usage_unknown, limit_reached, spend_unknown, spend_exceeded create_order com quantity: 7 onde checks diz at_most: {from: check_stock.in_stock} e o estoque é 5: refuse, condition_failed; um quarto pedido num dia limitado a 3: refuse, limit_reached
8 o portão do rulebook, como o rulebook compilado do tenant decide a chamada a regra do rulebook que a recusou um reembolso que o fluxo do rulebook recusa: refuse, com aquela regra como motivo e o seu fluxo no detalhe
9 marcas menos confiáveis do que a ferramenta exige, numa escrita; recusada aqui quando a taint_policy do tenant é refuse tainted_call com taint_policy: refuse, um reembolso proposto depois de ler a caixa de entrada: refuse, tainted_call
10 um token de confirmação que cobre exatamente esta chamada, com estes argumentos, neste ponto do histórico confirmed o mesmo reembolso, apresentado com o token que a pessoa aprovou: allow, confirmed
11 todo motivo pelo qual uma escrita precisa de uma pessoa, juntos; uma escrita sem nenhum deles ainda é confirmada, por write gate_clarify, argument_origin, tainted_call, irreversible_pivot, schema_partial, write o reembolso com um valor que o modelo escreveu: confirm, argument_origin: amount; o mesmo valor digitado pela pessoa: confirm, write
12–13 um desfazer que passou por todos os passos acima; senão, uma chamada que nada acima reteve compensation, allow get_order, uma leitura: allow; refund_card como o desfazer que uma execução que falhou envia: allow, compensation

Ela dá uma de três respostas:

  • allow: a ferramenta roda uma vez, com uma chave de idempotência.
  • confirm: uma pessoa é perguntada, vendo cada argumento e de onde veio o seu valor; a chamada é verificada de novo com o token assinado que a aprovação dela carrega (capítulo 35).
  • refuse: nada roda. Onde etapas anteriores da execução já tinham escrito algo, essas escritas são desfeitas, a última primeiro (capítulo 34).

O que é garantido, e o que não é

Garantido. Uma chamada recusada não roda nada. Uma chamada confirmada roda só com os argumentos exatos que a pessoa viu. O histórico não pode ser descartado, editado ou reordenado sem que a verificação diga history_invalid. Nenhum modelo é chamado a menos que as suas configurações encaminhem um turno a ele.

Não garantido. A verificação julga nomes, schemas, marcas e o histórico, nunca o que uma resposta significa (o capítulo 42 confere as respostas com o que foi feito). Uma ferramenta alcançável por fora do agente pode ser chamada contornando a verificação, a menos que o gateway fique na frente dela. Agent(..., verify=False) pula a prova.

O rulebook sob o qual ele roda

O rulebook: de um agente -- Colang 1.0 ou 2.x, uma planilha de regras ou a pasta que o stalwart rules compile escreveu -- é aquilo contra o que o seu grafo é provado e o que a sua verificação de ferramentas aplica, de modo que as regras provadas são as regras sob as quais o agente roda:

  • Na inicialização, antes do primeiro turno, o agente compila o seu rulebook no registro que a verificação de ferramentas avalia (POST /v1/rulebooks/compile, ou POST /v1/rule-sheets/compile para uma planilha), uma vez para o seu cliente; uma pasta que o stalwart rules compile escreveu é lida como está, sem chamada. Nada fica guardado no serviço: toda verificação leva o registro ao lado do catálogo do agente.
  • stalwart verify o compila do mesmo jeito e prova o grafo contra ele (capítulo 46).
  • Onde ele não pode ser compilado -- sem serviço, um rulebook que o compilador recusa (rulebook_unsupported, nomeando a linha), ou uma organização que só envia registros compilados (policy_input_refused) -- o agente não roda. Ele levanta RulebookNotHeld, dizendo por quê.

Os dois balcões da loja, com um rulebook que só deixa um reembolso rodar para um pedido que está pago:

define user ask refund
  "estorne o pedido 4411"
  "devolva o dinheiro do cliente"

define flow refunds
  user ask refund
  if $order_paid
    execute refund_card
  else
    bot refuse
# As duas centrais da loja, com um rulebook que decide quando um estorno pode rodar.
agents:
  desk:
    model: "anthropic:claude-sonnet-5-5"
    tools: [get_order, charge_card, send_receipt, read_inbox]
    hands_off_to: [billing]
    on_unread: refuse
  billing:
    privilege: high
    tools: [refund_card]
tools:
  - python: shop.tools
rulebook: ./policy.co                      # provado contra ele, e compilado para a verificação de ferramentas

A verificação de ferramentas decide sob ele, com o contexto que a sua aplicação fornece:

desk = Agent.from_file("agent.yaml")
catalogue = desk.catalogue()                    # as ferramentas dos dois balcões, feitas nesta máquina
rulebook = desk.rules().record                  # policy.co, compilado uma vez para este agente
with Client() as client:
    billing = client.session("billing", catalogue=catalogue, rulebook=rulebook)
    billing.read("estorne 40,00 do pedido 4411")
    for paid in (False, True):
        check = billing.check_tool_call("refund_card", {"order_id": "4411", "amount": 40.00},
                                        origins={"order_id": "typed", "amount": "typed"},
                                        context={"order_paid": paid})
        print(f"order_paid {paid}:", check.decision, ", ".join(check.reasons), check.detail.get("flow", ""))
order_paid False: refuse refuse refunds
order_paid True: confirm write 

O reembolso do pedido não pago é recusado pelo fluxo refunds do rulebook; o do pedido pago passa pelo rulebook e é confirmado, como toda escrita é.

As ações de um rulebook nomeiam ferramentas com execute <ferramenta>, como faz o fluxo refunds, ou com bot execute_<ferramenta>, a mesma chamada escrita como o registro a nomeia. Qualquer outra linha bot é algo que o bot diz, não uma chamada de ferramenta.

Os fatos do rulebook

Uma regra como if $order_paid lê um fato: verdadeiro ou falso, por nome, enviado com cada verificação. Um fato que nada informa é falso, então a regra recusa. Há dois jeitos de informar um, e um agente pode usar os dois:

  • O seu código diz, quando a sua aplicação sabe: Agent.from_file("agent.yaml", context=...), ou Agent(..., context=...), recebe os fatos como um mapeamento, ou como uma função do usuário que os devolve, chamada a cada verificação para dá-los como estão naquele momento. Uma verificação feita pelo cliente ou por uma sessão recebe context= em cada chamada (capítulo 24).
  • A própria ferramenta do agente diz: facts: no agent.yaml, ou Agent(..., facts=...), lê um fato de um campo do resultado de uma ferramenta, na resposta mais recente dessa ferramenta na conversa. A loja lê os seus dois fatos de get_order (capítulo 30):
facts:
  card_on_file: {from: get_order.card, same: order_id}
  order_paid: {from: get_order.status == paid, same: order_id}

from é <ferramenta>.<campo>, verdadeiro quando o campo é verdadeiro, um número diferente de 0, ou um texto, lista ou mapeamento não vazio; ou <ferramenta>.<campo> == <valor>, verdadeiro quando o campo é igual ao valor. Até a ferramenta responder, o fato é falso, então a cobrança da loja só é permitida depois que mostre o pedido 4411 mostrou um cartão.

same: order_id amarra o fato à chamada verificada: só conta uma resposta de get_order para o pedido que a chamada nomeia, então o status de um pedido nunca permite o reembolso de outro. Deixe-o de fora só para um fato sobre a conversa como um todo, como a conta do usuário. Um fato nunca é lido de uma ferramenta que reads_outside: qualquer um poderia escrever o que ela devolve. Onde o seu código e o arquivo informam o mesmo fato, vale o do arquivo.

Se você já usa LangGraph

O middleware de guarda, build_stalwart_graph e Guard.call_with também recebem context=, e stalwart_agents.facts.Context(facts=agent.facts) lê para eles os facts: do arquivo nas respostas das ferramentas (capítulo 27).

A prova

Os agentes são compilados num grafo (qls-graph/1) e no seu catálogo. Cada obrigação -- esta ferramenta nunca roda sem a sua verificação, esta escrita nunca roda sem confirmação depois de ler algo de fora -- é uma pergunta que o caçador responde sobre cada rota:

  • alcançável: um achado, com um caminho testemunha mapeado de volta à linha do agent.yaml (capítulo 31);
  • inalcançável: a obrigação vale, até a profundidade declarada (capítulo 31);
  • indeciso: relatado como indeciso, nunca como limpo.

Ela roda de três formas:

  • Automaticamente. Agent(..., verify=True), o padrão, prova o grafo no nosso serviço antes do primeiro turno, uma vez por grafo: uma reconstrução do mesmo grafo é respondida pelo cache. Um achado do tipo Vulnerabilidade levanta NotVerified, com os achados, e nada roda. Uma prova que não pôde rodar também levanta, nunca dá um resultado limpo.
  • Como portão de CI. stalwart verify agent.yaml faz o lint, compila, caça e imprime o relatório, e sai com 1 quando o portão reprova (capítulo 46). O agente da loja do capítulo 30:
$ stalwart verify agent.yaml
stalwart verify agent.yaml

Compiled the rulebook policy.co: the tool check enforces the rules verified here.
Lints from the runtime (none of these blocks the agent):
  write_not_retry_safe (warning) on charge_card: a write with no idempotency, so an ambiguous failure goes to a person
  write_without_result_schema (warning) on charge_card: a write with no result schema, so its outcome could not be reported
  compensation_not_retry_safe (warning) on charge_card: 'refund_card' undoes it and is not retry-safe
  write_not_retry_safe (warning) on refund_card: a write with no idempotency, so an ambiguous failure goes to a person
  write_without_result_schema (warning) on refund_card: a write with no result schema, so its outcome could not be reported
  write_without_result_schema (warning) on send_receipt: a write with no result schema, so its outcome could not be reported
  compensation_out_of_reach (warning) on charge_card: agent 'desk' can call it but not 'refund_card', which undoes it, so a run of that agent cannot undo it

Code checks: no finding in 4 file(s) (rules d656f9ab32d3).

Warnings: 2 findings that do not block this agent. Look at each and weigh it; the agent can run as it is.

QG023 Medium on cycle:desk_model (agent.yaml:3)
  the cycle through 'desk_model' calls the model or a tool, and nothing declares a budget: the framework's recursion limit of 60 is the run's only ceiling
  OWASP LLM06:2026, ASI08:2026
  Fix: Declare a budget for the agent or the run, or add an exit the model cannot avoid -- a counter in the state, declared on the edge as exit_after.

QG105 Review on refund_card (agent.yaml:10)
  the write 'refund_card' has no compensation, is not irreversible, and runs on a path with another write
  OWASP LLM03:2026, ASI08:2026
  Fix: Give 'refund_card' an undo -- `undo:` in agent.yaml or `compensation` in the catalogue, which may be the tool itself with its arguments swapped -- or say a failed run may leave it done (`undo: none`), or mark it irreversible.

Rules with nothing to read, so nothing raised:
  QG017: no argument is declared as code or a shell command
  QG018: no argument is declared as SQL, a query, a path, a URL or a template
  QG019: no tool is declared confidential
  QG020: no retrieval from a store declared multi-tenant
  QG021: no tool is declared to write a store declared persistent
  QG106: no tool declares a source or a server, and the catalogue declares no modules
  QG107: no agent in the catalogue declares a remote channel

Gate: passed. Nothing blocks this agent: the 2 warnings above are for you to look at and weigh.
$ echo $?
0
  • Pela API. Client().verify_graph(graph, catalogue=..., rulebook=..., session_steps=...) envia o grafo que desk.export() devolve e o catálogo que desk.catalogue() devolve, como um job graph (capítulo 47).

As regras que ela prova

Cada regra que a caça do grafo e do rulebook de um agente levanta, gerada a partir do catálogo do caçador. Reprova o portão é aquilo a que o stalwart verify submete um build; impede o Agent(...) é aquilo com que a prova automática se recusa a rodar:

Regra Título Tipo Reprova o portão Impede o Agent(...)
QG001 Missing check Vulnerabilidade sim sim
QG002 Escapable guard Vulnerabilidade sim sim
QG003 Multi-turn escape Vulnerabilidade sim sim
QG004 Permissive by default Bug não não
QG005 Unshippable policy Bug sim não
QG006 Unverified region Bug não não
QG007 Read before written Vulnerabilidade não sim
QG008 Call without execute Vulnerabilidade não sim
QG009 Undefined bot message Vulnerabilidade não sim
QG010 Dead action Vulnerabilidade não sim
QG011 Confused deputy Vulnerabilidade sim sim
QG012 Cascading injection Vulnerabilidade sim sim
QG013 Irreversible before undoable Bug não não
QG014 Order rule escapable Vulnerabilidade sim sim
QG015 Undeclared route Bug não não
QG016 Unmodelled path Vulnerabilidade sim sim
QG017 Model writes code that runs Vulnerabilidade sim sim
QG018 Model writes a query or a destination Vulnerabilidade não sim
QG019 Confidential data leaves Vulnerabilidade não sim
QG020 Retrieval not bound to the user Vulnerabilidade não sim
QG021 Untrusted content persisted Vulnerabilidade não sim
QG022 Rail with a published advisory Vulnerabilidade sim sim
QG023 Unbounded loop Bug não não
QG101 Unchecked action Ponto de atenção de segurança não não
QG104 Check missing against its peers Ponto de atenção de segurança não não
QG105 Unrecoverable write Ponto de atenção de segurança não não
QG106 Unpinned tool source Ponto de atenção de segurança não não
QG107 Unauthenticated agent channel Ponto de atenção de segurança não não
QG312 Engine disagreement Bug sim não

A página de cada regra diz o que ela prova e o que não prova.

Os limites

  • Um modelo escolhe livremente. A prova lê uma etapa de modelo como capaz de escolher qualquer ferramenta e qualquer argumento que receba; as verificações seguram o que ele escolhe, e nada prova que as escolhas dele sejam boas.
  • Etapas que não são nossas são opacas. Uma etapa que a sua equipe escreve é lida como capaz de fazer tudo o que as rotas declaradas dela permitem (capítulo 31).
  • Uma prova limpa vale até a profundidade que declara: o limite de etapas da execução e o número de turnos que o documento de achados nomeia.
  • A prova cobre o grafo, o catálogo e o rulebook, não o código das suas ferramentas; as verificações de código do capítulo 46 leem esse código só em busca de problemas de rede e de credenciais.
  • O rulebook decide só as ferramentas que os seus fluxos chamam por execute. Uma ferramenta que nenhum fluxo governa é decidida pelo resto da verificação.