Ir para o conteúdo

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.tools pega 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/mcp pega 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: ou digest: 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 de stalwart gateway envia o seu token (capítulo 37).
  • openapi: ./crm.yaml faz de cada operação de um documento OpenAPI uma ferramenta: GET e HEAD leem, e qualquer outro método escreve, a menos que a operação diga o contrário em x-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.