30 · O seu primeiro agente¶
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.
O Construtor de Bots é o pacote stalwart-agents. Você declara um agente num arquivo, ou em poucas linhas
de Python: as suas ferramentas, as palavras que dizem o que cada ferramenta faz, as suas regras, e para qual
agente ele pode transferir uma conversa. Você o verifica e o executa. Você não precisa aprender um framework
de agentes para nada disso, e esta parte do guia nunca pede que você aprenda.
Antes de começar¶
Você precisa de uma conta e da sua chave (capítulo 5), e da licença ativada uma vez em cada máquina que executa os seus agentes:
$ pip install "stalwart-agents[anthropic]" # ou openai, google, mistral, ollama; ou nenhum, sem modelo
$ export QLS_API_KEY=qls_live_...
$ stalwart licence activate
Nada no pacote funciona sem a chave e uma licença ativada, exceto ativá-la, stalwart doctor, que diz o
que falta, e stalwart check, que verifica um certificado. O que é uma licença, e o que funciona offline,
está no capítulo 38.
Onde o runtime está instalado, um turno e uma chamada de ferramenta não precisam de rede: o runtime responde aos dois na máquina que executa o seu agente.
O agente da loja, num arquivo¶
Este guia acompanha um agente ao longo da Parte I: o atendimento de uma pequena loja. Ele lê pedidos, cobra cartões, envia recibos por e-mail e lê a caixa de entrada do suporte, e transfere os reembolsos para um balcão de cobrança que tem mais privilégio:
# A central de atendimento da loja, e a central de cobrança para onde ela passa os estornos.
agents:
desk:
model: "anthropic:claude-sonnet-5-5" # o seu provedor; a chave vem do seu ambiente
tools: [get_order, charge_card, send_receipt, read_inbox]
hands_off_to: [billing]
on_unread: refuse # um turno que nenhuma ferramenta cobre é recusado, com o motivo
billing:
privilege: high
tools: [refund_card]
tools:
- python: shop.tools # cada função pública de shop/tools.py
rulebook: ./policy.co # as regras contra as quais cada chamada é verificada
facts: # o que são o $card_on_file e o $order_paid do rulebook
card_on_file: {from: get_order.card, same: order_id} # lido da resposta de get_order
order_paid: {from: get_order.status == paid, same: order_id} # para o pedido que a chamada cita
runs: atomic # uma execução que falha é desfeita, da última escrita para trás
storage: sqlite:///runs.db # onde ficam as execuções e as confirmações pendentes
As suas ferramentas são funções Python comuns. As anotações de tipo de uma função são os seus argumentos, a
primeira linha da sua docstring é o que ela faz, e @tool(...) lhe dá as palavras que as verificações leem.
Uma função sem palavras é uma leitura:
"""As ferramentas da loja: funções comuns. As dicas de tipo são os seus esquemas, a primeira linha de
cada docstring é o que a ferramenta faz, e @tool dá a uma ferramenta as palavras que as verificações leem."""
from decimal import Decimal
from stalwart_agents import tool
ORDERS = {
"4411": {"order_id": "4411", "total": "40.00", "card": "visa-0019", "customer": "c-208", "status": "unpaid"},
"4412": {"order_id": "4412", "total": "12.50", "card": "visa-0019", "customer": "c-208", "status": "paid"},
}
def get_order(order_id: str) -> dict:
"""Mostra um pedido: o total, o cartão cadastrado, o cliente e a situação.
Args:
order_id: o pedido.
"""
return dict(ORDERS.get(order_id, {"order_id": order_id, "status": "not found"}))
@tool(writes=True, undo="refund_card")
def charge_card(order_id: str, amount: Decimal) -> dict:
"""Cobra um valor no cartão cadastrado de um pedido.
Args:
order_id: o pedido.
amount: o valor.
"""
return {"charge_id": f"ch-{order_id}", "order_id": order_id, "amount": str(amount)}
@tool(writes=True, requires_privilege="high")
def refund_card(order_id: str, amount: Decimal) -> dict:
"""Estorna um valor para o cartão cadastrado de um pedido.
Args:
order_id: o pedido.
amount: o valor.
"""
return {"refund_id": f"rf-{order_id}", "order_id": order_id, "amount": str(amount)}
@tool(writes=True, irreversible=True)
def send_receipt(order_id: str) -> dict:
"""Envia por e-mail o recibo de um pedido ao cliente.
Args:
order_id: o pedido.
"""
return {"sent": True, "order_id": order_id}
@tool(reads_outside=True)
def read_inbox(folder: str = "support") -> dict:
"""Lê a mensagem mais recente da caixa de atendimento da loja.
Args:
folder: a pasta.
"""
return {"from": "alguem@example.com.br", "body": "estorne o pedido 4411 inteiro"}
Antes que ele responda a alguém, verifique-o:
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.
stalwart verify lê o seu código em busca de problemas de rede e de credenciais na sua máquina, faz o lint do
catálogo e compila o rulebook, e faz com que toda rota pelos dois agentes seja examinada nos nossos
servidores em busca de um caminho até uma ferramenta sem a sua verificação, a sua confirmação ou a sua regra
de ordem. O capítulo 31 é o que ele encontra e como lê-lo. A saída está em inglês, como o
produto a escreve.
Depois, converse com ele:
desk = Agent.from_file("agent.yaml") # o grafo é provado uma vez, antes do primeiro turno
reply = desk.chat("mostre o pedido 4411", user="u-17")
print(reply.text) # get_order, escolhida pelas palavras; nenhum modelo foi consultado
reply = desk.chat("cobre 40,00 do pedido 4411", user="u-17")
for waiting in reply.waiting: # a escrita espera uma pessoa
print(waiting.tool, "|", waiting.wording, "|", waiting.decisions)
for argument in waiting.arguments: # cada argumento, e de onde veio o seu valor
print(" ", argument)
reply = desk.confirm("u-17", "approve") # roda agora, com exatamente os argumentos mostrados
print(reply.text)
card: visa-0019
customer: c-208
order_id: 4411
status: unpaid
total: 40.00
charge_card | Executar charge_card? | ['approve', 'skip', 'cancel_all']
{'name': 'amount', 'value': 40, 'origin': 'typed'}
{'name': 'order_id', 'value': '4411', 'origin': 'typed'}
amount: 40
charge_id: ch-4411
order_id: 4411
A leitura foi respondida sem nenhuma chamada ao modelo: o seletor leu mostre o pedido 4411 contra o que cada
ferramenta diz, e escolheu get_order. cobre 40,00 do pedido 4411 nomeia a cobrança, o pedido e o valor, na
ordem em que uma pessoa os digita. A cobrança esperou por uma pessoa, mostrando cada argumento e de onde veio
o seu valor, e só foi executada depois de aprovada, com exatamente esses argumentos
(capítulo 32). O rulebook só permite uma cobrança if $card_on_file, e facts: lê isso da
resposta de get_order para o mesmo pedido, então a cobrança é permitida depois que o pedido foi mostrado com
um cartão (capítulo 45).
Para servi-lo por HTTP, com uma interface de chat em / que mostra confirmações, recusas e reversões:
$ stalwart serve agent.yaml
O capítulo 35 é a interface e as rotas por trás dela.
O mesmo agente em Python¶
Tudo o que o arquivo diz pode ser dito em Python, e significa o mesmo:
from stalwart_agents import Agent
from shop.tools import charge_card, get_order, read_inbox, refund_card, send_receipt
billing = Agent("billing", tools=[refund_card], privilege="high", rulebook="policy.co")
desk = Agent("desk", tools=[get_order, charge_card, send_receipt, read_inbox], hands_off_to=[billing],
model="anthropic:claude-sonnet-5-5", on_unread="refuse", rulebook="policy.co",
facts={"card_on_file": {"from": "get_order.card", "same": "order_id"},
"order_paid": {"from": "get_order.status == paid", "same": "order_id"}},
storage="sqlite:///runs.db")
from_file = Agent.from_file("agent.yaml")
print(desk.catalogue_digest() == from_file.catalogue_digest()) # o mesmo catálogo, de um jeito ou de outro
True
O arquivo e o agente em Python dão o mesmo catálogo, com o mesmo resumo criptográfico, para que uma equipe possa começar por um e passar para o outro.
O que agent.yaml diz¶
As palavras do topo do arquivo, de um agente, e de replies: e answers:, geradas a partir do pacote:
| Chave | O que diz |
|---|---|
agents |
os agentes, por nome; o primeiro é aquele com que uma conversa começa |
tools |
de onde vêm as ferramentas: python: as funções públicas de um módulo, mcp: o tools/list de um servidor, openapi: cada operação de um documento, http: as chamadas de um serviço web listadas à mão, code: o seu próprio código num espaço próprio e fechado, ou name: as palavras de uma ferramenta |
rulebook |
as regras contra as quais toda chamada de ferramenta é verificada e o grafo é provado -- um arquivo Colang, uma planilha de regras ou a pasta compilada dela |
facts |
o que é cada fato que o rulebook lê ($order_paid), lido de um campo do resultado de uma ferramenta: {from: get_order.status == paid, same: order_id} (capítulo 45) |
runs |
atomic (o padrão): uma execução que falha é desfeita, da última escrita para a primeira; stop: uma execução que falha fica como está |
storage |
onde as execuções e as confirmações pendentes são guardadas: sqlite:///runs.db, ou postgres://... para vários workers; em memória quando não informado |
stores |
os armazenamentos que as ferramentas leem e escrevem, como uma memória ou um índice de busca, e como cada um é compartilhado |
budget |
limites para o laço de uma execução, que a regra do caçador de bugs sobre laços sem limite lê |
modules |
os módulos que o código escrito por um modelo pode importar, que as regras do caçador de bugs sobre código escrito por modelo leem |
replies |
as configurações da verificação de respostas (capítulo 42) |
answers |
respostas fundamentadas a partir dos seus documentos (capítulo 44) |
vocabulary |
as palavras do próprio bot, que o construtor de bots deriva do dicionário para as ferramentas que você escolheu: um arquivo que ele grava ao lado do agent.yaml, recusado se foi alterado depois (capítulo 20) |
job |
a descrição do trabalho do bot: title, purpose, a quem ele atende em serves (customers, staff ou ambos), o seu manager e escalates_to (cada um, um papel), a sua team e o seu horário em hours |
tasks |
as tarefas do bot, por nome: cada uma com o seu title, as tools que usa, o seu output (reply, document, draft ou action), onde começa na escada (watch ou suggest), os triggers que a iniciam, e se as suas mudanças rodam in_order ou all_at_once (steps) |
triggers |
como o trabalho chega sozinho, por nome: cada um do kind email, form, schedule, webhook, change ou message; o timing de um agendamento é around, exact ou always_on; o where de uma mudança aceita condições ou um filtro; uma mensagem chega in teams, slack, whatsapp, instagram, messenger, sms ou custom, numa account; um webhook ou uma mensagem custom pode vir de um dos own_apps do bot |
ladder |
a régua que uma tarefa precisa ultrapassar para subir na escada (no mínimo proposals 50, agreement 0,95 e drift_free_days 14: pode subir, nunca descer) e a sample das execuções em Agir e avisar que uma pessoa revisa |
drift |
quando o trabalho passado é repetido para vigiar desvios (replay: weekly ou on_model_change) e os thresholds a partir dos quais um sinal é relatado |
site |
a página do próprio bot: on, off, ou o seu title e language |
own_apps |
programas que o dono executa nesta máquina ou na rede local, por nome: cada um com o address do seu servidor MCP e o seu secret; nos planos pago e empresarial |
withdrawn |
ferramentas que o agente não chama mais: recusadas antes de qualquer outra coisa e nunca oferecidas, até o arquivo deixar de citá-las |
settings |
as configurações da verificação de ferramentas, levadas em cada verificação: taint_policy (confirm, ou refuse para uma chamada que leva um rótulo em que não se confia) e confirmation_ttl_seconds, quanto tempo vale o sim de uma pessoa |
agents.<name>.model |
o modelo do agente, provedor:modelo, usado com a sua própria chave do seu ambiente; deixe-o de fora e nenhum modelo é chamado |
agents.<name>.tools |
as ferramentas que este agente pode chamar, por nome |
agents.<name>.hands_off_to |
os agentes para os quais este pode passar a conversa |
agents.<name>.on_unread |
o que acontece com um turno que nada lê, depois de a pessoa ter sido convidada a dizê-lo de outra forma: refuse, model, ask ou answer; a configuração do console quando não informado |
agents.<name>.privilege |
low, standard ou high: o grau de confiança dado a uma transferência vinda deste agente |
agents.<name>.remote |
como uma transferência para este agente viaja quando ele roda em outro lugar, e como esse canal é autenticado |
agents.<name>.budget |
os limites próprios deste agente para uma execução: max_steps, max_tool_calls e max_model_calls, no lugar dos do arquivo |
agents.<name>.system |
o que é dito ao modelo antes da conversa |
agents.<name>.job |
a descrição do trabalho deste agente, com as mesmas palavras do job do topo |
agents.<name>.defaults |
valores que os argumentos das ferramentas recebem sempre que um turno não os dá, pelo nome do parâmetro -- a conta em que uma ferramenta age, por exemplo --, para que o roteador nunca os pergunte |
replies.rules |
o arquivo de regras de resposta, como a página Reply rules do console o salva |
replies.phrasings |
o arquivo de frases de resultado |
replies.composer |
model: o modelo escreve a resposta e a verificação a confere; template: a resposta são as frases, e nenhum modelo a escreve |
replies.language |
a língua das frases padrão: en ou pt |
replies.temperature |
true: as palavras do próprio bot numa resposta (aberturas, ligações e fechos, e as suas palavras para o verbo de uma ferramenta) variam conforme a conversa e o turno, então uma conversa repetida sai igual; um valor, uma negação, uma resposta exata ou as palavras de uma fonte nunca variam. false, o padrão: toda resposta nas próprias palavras das frases |
replies.audience |
para quem são as respostas. customer, o padrão: um valor que nenhum registro de ferramenta tem sai da resposta; staff: ele fica, destacado. Em ambos os casos cada valor é rastreado até a chamada e o campo de onde veio, e o rastro vai junto da mensagem da resposta |
answers.index |
o índice de fontes que o stalwart sources build escreve |
answers.k |
no máximo este número de trechos; 3 quando nem o arquivo nem o console dizem |
answers.floor |
nenhum trecho com pontuação abaixo disto é usado, de 0 a 1; 0,3 quando nenhum dos dois diz |
answers.composer |
extract, as próprias frases dos documentos; ou compose, o seu modelo escreve a partir delas |
Uma palavra que o arquivo não conhece é um erro na sua linha, nunca ignorada (abaixo).
O capítulo 48 lista as chaves sob cada uma destas, em todo nível, e as do
gateway.yaml.
Em Python, load("agent.yaml") lê o arquivo num AgentFile: os agentes, as suas ferramentas, os
armazenamentos e o rulebook que ele declara. Sem fetch=True, as ferramentas de um servidor MCP são
conhecidas pelos nomes que o arquivo lista.
De onde vêm as ferramentas¶
python: shop.toolspega toda função pública do módulo, importada da pasta onde o arquivo está. O seu esquema vem das suas anotações de tipo.mcp: https://tools.internal/mcppega as ferramentas que um servidor MCP lista, com os seus esquemas e as indicações que o servidor dá: uma ferramenta que o servidor marca como somente leitura lê, uma que ele marca como destrutiva escreve, e uma sobre a qual ele nada diz escreve.tools: [...]mantém só as nomeadas;version:oudigest:fixa o servidor. Cada usuário do seu agente tem uma sessão MCP própria no servidor, para que o que um servidor guarda para um usuário nunca chegue a outro.headers:envia um cabeçalho em toda requisição, com o seu${NOME}lido do seu ambiente quando a requisição é feita: é assim que um agente atrás destalwart gatewayenvia o seu token (capítulo 37).openapi: ./crm.yamlfaz de cada operação de um documento OpenAPI uma ferramenta:GETeHEADleem, e qualquer outro método escreve, a menos que a operação diga o contrário emx-stalwart.name: refund_card, com palavras ao lado, acrescenta palavras a uma ferramenta de qualquer origem.
O capítulo 21 traz as outras formas de entrada, como as funções exportadas de um projeto TypeScript.
As palavras de uma ferramenta¶
As palavras são o que transforma uma função numa ferramenta que o produto pode governar. Cada uma define um campo do catálogo que as verificações leem:
| Palavra | Campo do catálogo | O que o produto passa a fazer |
|---|---|---|
writes |
read_only (its negation) |
a ferramenta muda alguma coisa, então a sua chamada é verificada contra as suas regras, e espera por uma pessoa onde elas mandam ou onde nada a permite |
undo |
compensation |
nomeia a ferramenta que desfaz esta escrita; uma execução que falha a chama, verificada como qualquer chamada |
irreversible |
irreversible |
a escrita não pode ser desfeita; a sua confirmação diz isso, e um rollback a deixa no lugar |
retry_safe |
idempotent |
chamá-la duas vezes com os mesmos argumentos faz o que chamá-la uma vez faz, então uma chamada de resultado desconhecido pode ser repetida |
reads_outside |
reads_outside |
o que ela devolve vem de fora, um e-mail ou uma página web, e não é confiável; o que vem depois carrega essa marca |
sends_out |
egress |
ela pode enviar para uma URL ou outro destino; um argumento de destino é recusado em qualquer ferramenta que não diga isso |
after |
order.after |
a ferramenta só roda depois de cada ferramenta nomeada, na mesma sessão |
not_after |
order.not_after |
a ferramenta nunca roda depois de uma ferramenta nomeada, ou depois de qualquer coisa de fora quando ela nomeia untrusted |
at_most |
order.at_most, limits |
a ferramenta roda no máximo este número de vezes numa sessão; {count, per}, ou uma lista deles, um por período, que o seu host conta e a verificação recusa além do limite |
confirm_with |
confirmation_wording |
o que se pergunta a uma pessoa antes de a chamada rodar, nas suas palavras; sobre uma ferramenta irreversível que não dê nenhuma, pergunta-se Run <tool>? Nothing undoes it once it has run. |
requires_privilege |
requires_privilege |
só um agente com pelo menos este privilégio pode chamá-la |
description |
description |
o que a ferramenta faz, contra o que o seletor lê os turnos; a primeira linha da docstring quando não informado |
checks |
checks |
condições que cada chamada precisa cumprir, como uma quantidade no máximo igual ao estoque; uma chamada que não as cumpre é recusada, em palavras, antes de rodar |
filter |
filter |
numa leitura, quais registros ela repassa: each nomeia a lista, keep as condições que cada registro cumpre; os outros nunca chegam ao agente |
spend |
spend |
o máximo que um campo pode somar por período, com as ferramentas do mesmo pool dividindo uma contagem; uma chamada que passaria do limite é recusada antes de rodar |
currency |
currency |
a moeda dos valores da ferramenta, como USD ou BRL; obrigatória com spend |
outcome |
outcome |
numa escrita, a leitura que a acompanha até o fim e como são feito e falhou; uma escrita que falha segue a política de falha da tarefa, e uma nunca confirmada vira uma pergunta |
personal_data |
personal_data |
se o que a ferramenta devolve são dados pessoais: none, personal ou special; uma ferramenta que não diz nada é tratada como personal, que um modelo só lê no plano enterprise e nunca num modelo gratuito na nuvem |
argument_kinds |
argument_kinds |
o que um argumento é, como code, sql, path ou url, que as regras do caçador de bugs sobre valores escritos pelo modelo leem |
sandboxed |
sandboxed |
o código que a ferramenta roda é contido, o que baixa a severidade de um achado sobre ele |
sanitised_by |
sanitised_by |
a verificação que limpa um argumento antes de a ferramenta usá-lo |
confidential |
confidential |
o que a ferramenta devolve é confidencial, e para quem |
confidential_fields |
confidential_fields |
quais campos do que ela devolve são confidenciais |
subject_argument |
subject_argument |
o argumento que nomeia de quem são os dados que a ferramenta devolve |
retrieves |
retrieves |
o armazenamento de onde a ferramenta lê, e o argumento que delimita as suas leituras |
persists |
persists |
o armazenamento em que a ferramenta escreve |
source |
source |
de onde vem o código da ferramenta, que a regra do caçador de bugs sobre fontes não fixadas lê |
server |
server |
o servidor MCP que serve a ferramenta, com a sua versão ou digest |
also_called |
also_called |
palavras que uma pessoa usa para aquilo sobre o que a ferramenta age, lidas como as palavras do seu nome |
working |
is_working |
uma escrita só nos arquivos de trabalho do próprio bot, para uso do dono, que roda sem a aprovação de uma pessoa, como uma resposta; só as ferramentas do próprio Stalwart podem dizer isso |
named_by |
named_by |
os nomes pelos quais uma pessoa chama esta fonte, quando ela só é buscada se a mensagem a nomear |
otherwise |
otherwise |
a ferramenta buscada no lugar desta quando a mensagem não diz nenhum dos nomes dela |
Você não precisa da maioria delas para começar. Uma leitura não precisa de nenhuma; uma escrita precisa de
writes=True; uma escrita que pode ser desfeita precisa de undo=; uma que não pode precisa de
irreversible=True. As demais tornam as verificações mais precisas, e o caçador de bugs avisa quando falta
uma (capítulo 31).
TOOL_WORDS guarda o nome de cada palavra, para código que escreve o arquivo.
Agentes, privilégio e transferências¶
Um agente pode transferir uma conversa para outro que ele nomeia em hands_off_to. Cada agente tem as suas
próprias ferramentas, e só chama essas. privilege diz quanto uma transferência vinda de um agente merece
confiança: uma instrução que entrou num agente de baixo privilégio, por um e-mail por exemplo, é marcada
quando chega a um de alto privilégio, e uma escrita a que ela leve é confirmada por uma pessoa
(capítulo 33).
budget: {max_steps: 15}, num agente ou para o arquivo inteiro, é onde uma execução para: o do próprio
agente quando ele diz um, senão o do arquivo, senão 60 passos. Um laço que o modelo conduz pelas suas
ferramentas termina ali, e stalwart verify confere que termina (QG023, capítulo
31). max_tool_calls e max_model_calls limitam o que um turno chama: a chamada que
passaria do limite não é feita, e o turno para com BudgetSpent, um RunTooLong. Uma confirmação segue com a
contagem do seu turno, e um desfazer nunca é contado.
O modelo¶
model: "anthropic:claude-sonnet-5-5" nomeia um provedor e um modelo; a chave é lida do seu ambiente, na
variável de costume do provedor, e continua sua. Uma chave que falta é informada antes de qualquer envio,
com o nome da variável. Um modelo também pode ser uma função Python comum que recebe a conversa e devolve a
resposta. Não guardamos nenhuma credencial de modelo, em lugar nenhum.
O modelo só é chamado onde as suas configurações mandam um turno para ele. Todo turno é lido primeiro
pelo seletor, e um turno que nomeia uma ferramenta, que precisa de uma pergunta, ou que é recusado nunca
chega ao modelo. Um turno que nada lê recebe um pedido para ser dito de novo, uma vez; depois on_unread
decide: refuse, model, ask (a pessoa diz se o modelo pode recebê-lo) ou answer, a partir dos seus
documentos (capítulo 44). Sem model, com on_unread: refuse, sem hands_off_to,
com respostas a partir das suas formulações e respostas nas próprias frases dos seus documentos, nenhum
modelo é chamado jamais (capítulo 45).
Execuções e armazenamento¶
runs: atomic, o padrão, desfaz uma execução que falhou, a última escrita primeiro
(capítulo 34). storage: é onde as execuções e as confirmações pendentes ficam guardadas,
para que uma confirmação possa ser respondida depois de um reinício, ou em outro processo:
sqlite:///runs.db para uma máquina, postgres://... para várias.
Os comandos¶
Gerados a partir do próprio comando stalwart:
| Comando | Recebe | O que faz |
|---|---|---|
stalwart verify |
<file>, --keep, --dependencies, --replay, --export-only, --no-register |
as verificações de código aqui, depois o catálogo registrado com o seu rulebook, e o grafo e o rulebook dos agentes caçados nos nossos servidores; sai com 1 quando o portão reprova (capítulo 46) |
stalwart serve |
<file>, --host, --port, --agent, --operator-token, --dev-pair, --public-url, --sleeps, --follows, --console-origin |
os agentes por HTTP, com a interface em / (capítulo 35) |
stalwart trace |
uma execução mostrada a partir dos seus spans, ou só do armazenamento de execuções: show, html ou diff (capítulo 36) |
|
stalwart rollback |
uma execução parada desfeita a partir do seu próprio checkpointer: o que ela mudou é desfeito, do mais novo ao mais antigo, com --thread nomeando a execução |
|
stalwart redteam |
<file>, --graph, --mcp, --writes, --hunter, --export-only, --keep |
campanhas de ataque contra o que o verify encontrou, ou abertas, com toda ferramenta substituída por um stub (capítulo 33) |
stalwart gateway |
o gateway MCP, com toda chamada verificada no caminho até a ferramenta (capítulo 37) | |
stalwart mcp |
o servidor MCP do guardrail, para o seu assistente de IA: o que ele verifica, construir um, e medi-lo nas suas próprias chamadas (o guardrail) | |
stalwart doctor |
a chave, o serviço, o streaming pela sua rede, o runtime local, a licença e a cota que resta; funciona sem chave | |
stalwart check |
<certificate>, --cnf, --json, --print-source |
um certificado conferido aqui, offline, pelo verificador de referência; não precisa de chave (capítulo 7) |
stalwart catalogue |
import |
um catálogo a partir de um documento OpenAPI ou de um projeto TypeScript, com o seu código verificado antes (capítulo 21) |
stalwart rules |
compile |
uma planilha de regras compilada e verificada, e salva com --save (capítulo 41) |
stalwart sources |
build ask |
os documentos de que os seus agentes respondem, indexados e consultados aqui (capítulo 44) |
stalwart models |
fetch |
os modelos do bot, baixados antes de ele começar, com uma barra de progresso; funciona sem chave |
stalwart licence |
activate request answer install release status, <file>, --output |
a licença desta máquina, incluindo a ativação offline; funciona sem chave (capítulo 38) |
Os erros¶
Todo erro que o pacote levanta nomeia o que mudar, nas palavras de agent.yaml, nunca nos termos das
bibliotecas por baixo:
| Erro | Quando, e o que fazer |
|---|---|
StalwartError |
todo erro do pacote é um destes, então um único except StalwartError captura todos |
AgentFileError |
o agent.yaml diz algo que esta versão não consegue ler; a mensagem nomeia o arquivo e a linha |
HunterUnavailable |
a verificação não pôde rodar, então nada foi verificado; nunca é um resultado limpo |
LicenceRequired |
nenhuma chave, ou nenhuma licença ativada nesta máquina: defina QLS_API_KEY e rode stalwart licence activate |
ModelKeyMissing |
o provedor do modelo precisa de uma chave que o seu ambiente não tem; ele nomeia a variável, e nada foi enviado |
ModelUnavailable |
o modelo nomeado não pode ser usado aqui; a mensagem diz o que instalar ou mudar |
NotVerified |
o agente tem um caminho que escapa das suas verificações; findings nomeia cada um, e nada rodou |
RulebookNotHeld |
o agente declara um rulebook que a verificação de ferramentas não tem, e ele não pôde ser registrado; nada roda |
RunTooLong |
uma execução deu mais passos do que o seu limite: aumente max_steps, ou dê ao agente menos ferramentas para escolher |
StateConflict |
dois passos de uma execução escreveram na mesma parte do seu estado ao mesmo tempo; ele os nomeia |
StorageError |
o armazenamento em que as execuções são guardadas não pôde ser lido ou escrito; ele o nomeia |
ThumbRefused |
um polegar que registraria um caso falso, recusado, dizendo o que avaliar em seu lugar; nada foi enviado |
ToolSaid |
uma ferramenta recusou o que lhe pediram e disse por quê, como faz a resposta de erro de um servidor MCP; a execução para e a pessoa vê as palavras da própria ferramenta |
ToolUnavailable |
um agente lista uma ferramenta sem nada para rodá-la: declare a sua fonte, python, mcp ou openapi |
Um erro de arquivo nomeia a sua linha:
agent.yaml:7: agent 'desk' lists 'refund', which no tools source declares
Se você já usa LangGraph
Por baixo, o agente roda como um grafo LangGraph. desk.graph() o devolve, compilado e verificado,
para uma equipe que queira estendê-lo com nós próprios; nada na Parte I precisa dele. O
capítulo 27 é a porta para essa equipe, escrita nos
termos do próprio LangGraph.
Os limites¶
O que o stalwart-agents decide, e o que continua sendo seu:
- Ele decide qual ferramenta um turno nomeia, se uma chamada pode ser executada, se espera por uma pessoa ou se é recusada, o que é desfeito quando uma execução falha, e o que uma resposta pode dizer sobre o que foi feito.
- As suas funções decidem o que fazem. As palavras de uma ferramenta são tomadas como você as declara: uma ferramenta declarada como leitura que escreve é governada como leitura. As verificações de código leem o seu código em busca de problemas de rede e de credenciais, e nada mais sobre o que ele faz (capítulo 46).
- O seu modelo decide o que diz e qual ferramenta pede, onde as suas configurações permitem. Toda chamada que ele pede é verificada; as suas escolhas não são provadas boas (capítulo 31).
- Uma ferramenta alcançável fora do agente pode ser chamada por fora dele. As verificações governam as chamadas que o agente faz; o capítulo 37 põe a mesma verificação na frente das suas ferramentas para todo o resto.