Ir para o conteúdo

45 · Protegido e provado

Idiomas

O seletor entende turnos escritos em inglês. O português é a sua próxima gramática e ainda não foi lançado; até lá, um turno escrito em português não é lido como português.

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, ou o qls-router que você mesmo roda -- 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 nem o runtime nem o seu roteador conseguem 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 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 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
8 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
9 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
10 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
11–12 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) é aquilo contra o que o seu grafo é provado, e ele é registrado com o catálogo para que a verificação de ferramentas o aplique, de modo que as regras provadas são as regras sob as quais o agente roda:

  • stalwart verify registra o catálogo com o rulebook, compilado no registro que a verificação de ferramentas avalia (capítulo 46).
  • Na inicialização, antes do primeiro turno, um agente compara o digest do rulebook na versão registrada mais recente com o SHA-256 dos bytes do arquivo. Iguais, nada é registrado; diferentes ou ausente, o catálogo é registrado de novo com o rulebook antes que o agente rode.
  • Sem rulebook:, o agente ainda registra o seu catálogo na inicialização quando a versão mais recente não contém as suas ferramentas, cada uma como leitura ou escrita conforme declarada, para que a verificação de ferramentas as conheça.
  • Onde ele não pode ser registrado -- sem serviço, um rulebook que o compilador recusa (rulebook_unsupported, nomeando a linha), ou uma organização que só registra rulebooks como 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
  "refund order 4411"
  "give the customer their money back"

define flow refunds
  user ask refund
  if $order_paid
    execute refund_card
  else
    bot refuse
# The shop's two desks, with a rulebook that decides when a refund may run.
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                      # proved against, and registered for the tool check

A primeira inicialização o registra; a segunda o encontra já registrado:

desk = Agent.from_file("agent.yaml")
desk.chat("show me order 4411", user="u-17")   # before this first turn, the rulebook is registered

with open("policy.co", "rb") as policy:
    digest = hashlib.sha256(policy.read()).hexdigest()
with Client() as client:
    held = client.catalogue()                   # the newest catalogue version
    print("version", held.version, "holds policy.co:", held.rulebook_ir["policy_sha256"] == digest)

again = Agent.from_file("agent.yaml")           # a second start of the same agent
again.chat("show me order 4412", user="u-18")
with Client() as client:
    print("version", client.catalogue().version, "after a second start")

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

with Client() as client:
    billing = client.session("billing")
    billing.read("refund order 4411 40.00")
    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", ""))
version 1 holds policy.co: True
version 1 after a second start
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. Uma linha bot é algo que o bot diz, não uma chamada de ferramenta: um fluxo que só diz bot execute_refund_card não governa nenhuma ferramenta.

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 registra, 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

Registered as catalogue version 1, with the rulebook policy.co: the tool check enforces the rules verified here.
Lints at registration (nothing fails on a lint):
  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_without_compensation (warning) on refund_card: a write neither compensated nor marked irreversible
  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_not_retry_safe (warning) on send_receipt: a write with no idempotency, so an ambiguous failure goes to a person
  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 4b48b0081360).

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: Declare a compensation for 'refund_card' in the catalogue, or mark it irreversible.

QG105 Review on refund_card (policy.co:19)
  the write 'refund_card' has no compensation, is not irreversible, and runs on a path with another write
  OWASP LLM03:2026, ASI08:2026
  Fix: Declare a compensation for 'refund_card' in the catalogue, 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
$ 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 sim sim
QG019 Confidential data leaves Vulnerabilidade sim sim
QG020 Retrieval not bound to the user Vulnerabilidade sim sim
QG021 Untrusted content persisted Vulnerabilidade sim 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.