stalwart-agents
Crie o seu próprio agente de IA, poderoso mas sob controle: as suas ferramentas e as suas regras, verificadas na sua própria máquina. Declare-o em um único arquivo -- as suas ferramentas, as suas regras e os seus usuários --, depois verifique-o e execute-o. Você não precisa aprender um framework de agentes para nada disso.
Antes de começar. Você precisa de uma conta Stalwart e da sua chave de API. Cadastre-se em
stalwart.it: uma conta gratuita é aberta com a sua
primeira chave, no console. Defina a chave como QLS_API_KEY e execute stalwart licence activate
uma vez em cada máquina que executa os seus agentes. O que cada plano inclui está em
stalwart.it. Nada no pacote funciona sem a chave e uma licença
ativada, exceto a própria ativação da licença, o stalwart doctor, que diz o que falta, e o
stalwart check, que verifica um certificado offline
(a licença e o runtime).
$ pip install stalwart-agents # everything: the agents, the client, the gateway, the runtime
$ pip install "stalwart-agents[anthropic]" # with a model provider: anthropic, openai, google, mistral or ollama
$ pip install "stalwart-agents[all]" # with every provider
A documentação está em api.stalwart.it/pt/sdk, em português e
em inglês. Comece por
Construa um agente: um agente seu, do cadastro ao
stalwart serve.
O runtime local é distribuído para Linux (x86-64 e arm64), macOS em Apple silicon e Windows
(x86-64). Ele lê cada turno e verifica cada chamada de ferramenta na sua própria máquina, sob a
sua licença. Em qualquer outro lugar -- um Mac Intel, Alpine, Windows em ARM --, o mesmo comando
instala o pacote sem ele, e os planos e as verificações vêm do qls-router enterprise que você
executa no seu próprio ambiente (QLS_ROUTER_URL). A API hospedada da Stalwart não responde a
nenhum dos dois: sem runtime e sem roteador, um plano ou uma verificação levanta
SelectorUnavailableError, dizendo qual deles configurar.
# agent.yaml
agents:
desk:
model: "anthropic:claude-sonnet-5-5" # your provider and key, from your environment
tools: [get_order, charge_card, refund_card, read_inbox]
hands_off_to: [billing]
billing:
privilege: high
tools: [issue_refund]
tools:
- python: shop.tools # plain functions, schemas from their type hints
- mcp: https://tools.internal/mcp # an MCP server's tools/list
receipts: true # behind a gateway that admits a call on the agent's own check
- openapi: ./crm.yaml # each HTTP operation a tool
- name: refund_card
writes: true
rulebook: ./policy.co # Colang 1.0 or 2.x
runs: atomic # a failed run is undone
storage: sqlite:///runs.db # where runs are kept; postgres://... for several workers
Ou em Python, com o mesmo significado:
from stalwart_agents import Agent, tool
@tool(writes=True, undo="refund_card")
def charge_card(order_id: str, amount: Decimal) -> dict: ...
desk = Agent("desk", tools=[get_order, charge_card, refund_card], rulebook="policy.co",
model="anthropic:claude-sonnet-5-5")
reply = desk.chat("refund order 4411", user="u-17")
if reply.waiting: # a write waits for the person
reply = desk.confirm("u-17", "approve")
Antes de um turno ser executado, o grafo dos agentes é provado uma vez (verify=False o executa
sem prova). Um turno que nada lê é pedido de novo uma vez; depois, on_unread decide: refuse,
model, ask, em que a pessoa diz se o modelo pode recebê-lo (desk.confirm(user, "approve")
ou "decline"), ou answer, a partir dos seus documentos (abaixo). Um pedido que nenhuma
ferramenta cobre -- um cálculo, por exemplo -- é para isso que serve model. Com refuse, sem
hands_off_to, composer: template para as respostas e composer: extract para as respostas a
perguntas, nenhum modelo é chamado
(exemplos completos).
Protegido e provado. Duas verificações cercam o modelo, ambas decididas por código nativo no
seu próprio host: o router decide se um modelo é chamado, e cada chamada de ferramenta é
verificada -- allow, confirm ou refuse -- quer o router quer o modelo a tenha escolhido.
Antes do primeiro turno o grafo é provado: cada caminho até uma escrita sem a sua verificação, a
sua confirmação ou a sua regra de ordem é um achado, e Agent(...) levanta NotVerified sem
que nada rode. O rulebook contra o qual ele é provado é aquele sob o qual ele roda: na
inicialização, e a cada stalwart verify, ele é registrado com o catálogo para que a
verificação de ferramentas o aplique (RulebookNotHeld onde não puder ser).
from stalwart_agents import Agent, NotVerified
desk = Agent.from_file("agent.yaml")
try:
desk.chat("refund order 4411", user="u-17")
except NotVerified as refused: # nada rodou
for finding in refused.findings:
print(finding["rule"], finding.get("action")) # QG012 issue_refund
$ stalwart verify agent.yaml --replay # no CI: sai com 1 quando o portão reprova
Registered as catalogue version 7, with the rulebook policy.co: the tool check enforces the rules verified here.
QG012 High on issue_refund (agent.yaml:7)
Replay: reproduced
Gate: failed
No CI. O stalwart verify roda as verificações de código na sua máquina e o caçador de bugs
nos servidores da Stalwart -- nada além do pacote é instalado. Um runner ativa a sua licença
primeiro e a libera por último:
env:
QLS_API_KEY: ${{ secrets.QLS_API_KEY }}
steps:
- uses: actions/checkout@v4
- run: pip install stalwart-agents
- run: stalwart licence activate
- run: stalwart verify agent.yaml --replay --keep findings/ --dependencies uv.lock
- if: always()
run: stalwart licence release
Sair com 0 é um portão aprovado, 1 um reprovado, 2 um arquivo que ele não consegue ler ou a falta de chave, e 3 uma caçada que não pôde rodar -- nunca um resultado limpo (o comando por completo).
Cada verificação, cada garantia e o que é provado, com exemplos: Protegido e provado.
Regras sem Colang. As regras sobre as suas ferramentas podem ser uma planilha YAML que uma pessoa lê em voz alta, compilada para o mesmo rulebook em que uma política Colang se torna:
# rules.yaml
rules:
- action: issue_refund
requires: [supervisor_approved]
when: { amount_over: 200 }
- action: close_account
requires_confirmation: true
- action: delete_customer
never: true
- action: send_statement
after: [verify_identity]
stalwart rules compile rules.yaml a compila e a confronta com o seu catálogo mais recente -- uma
regra para uma ação que o catálogo não tem, uma escrita que a planilha nunca nomeia --, e --save
a registra no catálogo, onde toda verificação de ferramenta a aplica. Uma construção que o
compilador não consegue expressar com exatidão é recusada com a sua linha, nunca aproximada.
Respostas fiéis ao que foi feito. replies: confronta cada resposta com a execução que ela
segue:
replies:
rules: reply-rules.yaml # personal data by class, phrases never to say, allowed offers
phrasings: phrasings.yaml # how each write's outcome is told: done, failed, refused, ...
composer: model # or template: the reply is the phrasings, and no model writes it
Cada frase que fala de uma escrita é confrontada com o resultado dessa escrita -- feita só se ela rodou, uma falha ou recusada nunca dita como feita --, e cada valor, data, identificador ou endereço nela, com um valor que a chamada enviou ou devolveu. Uma frase que informa errado é substituída pela frase do resultado, e uma escrita que a resposta omite é acrescentada. Os dados pessoais que as regras retêm são mascarados, ou a resposta é retida. Isso roda no runtime local, e só contagens saem da máquina.
Respostas a partir dos seus documentos. Uma pergunta que nenhuma ferramenta responde pode ser respondida a partir dos seus próprios documentos, nunca pelo efeito colateral de uma ferramenta: a documentação que descreve um reembolso nunca é um reembolso.
answers:
index: sources.json # `stalwart sources build docs/*.md --out sources.json`
composer: extract # the documents' own sentences; compose: your model writes from them
agents:
desk:
on_unread: answer
Cada frase cita o trecho em que se apoia, e a verificação de suporte remove aquela que afirma um
número, uma data, um valor, um nome ou uma negação que os seus trechos não afirmam. Ela verifica
esses fatos, não o sentido em torno deles: uma frase que mantém os fatos de um trecho e tira uma
conclusão que o trecho não tira não é detectada, e é por isso que extract -- as próprias frases
dos trechos -- é o padrão. compose exige um plano que inclua respostas compostas.
Cálculos no seu host. Um turno que precisa de um cálculo que nenhuma ferramenta responde --
qual é o preço médio dos meus pedidos -- é uma etapa de cálculo, em todos os planos: as
ferramentas que ela lê rodam, o seu próprio modelo escreve uma função Python a partir da forma
dos dados (nunca dos seus valores), o runtime a admite -- nenhum socket, nenhum arquivo, nenhum
import fora de uma lista curta -- e a imagem qls-sandbox a executa num contêiner novo, sem rede.
Precisa de um mecanismo de contêineres: python -m stalwart_agents.sandbox baixa a imagem que esta
versão executa, e sem o mecanismo ou a imagem o turno é recusado, com um motivo que começa com
compute off:.
O que chega aos servidores da Stalwart. As conversas, as chamadas de ferramentas, os dados, a
função e o resultado de um cálculo, os seus documentos e o índice deles ficam no seu host. O que é enviado -- um solve e as suas entradas, as
descrições das ferramentas de um catálogo, uma planilha de regras, um prompt de geração de
guardrail e o registro de feedback de um polegar, que leva as palavras do turno que avalia -- é
antes analisado em busca de endereços de e-mail, números de telefone, números de cartão de
pagamento, IBANs, números do National Insurance do Reino Unido e de CPF, e de quaisquer
identificadores que você liste como seus (Client(own_identifiers=[...])). Uma ocorrência faz com
que nada seja enviado: PersonalDataError nomeia o tipo e o lugar de cada ocorrência, nunca o seu
valor.
O polegar de uma pessoa em uma resposta é feedback com que o minerador hospedado aprende as palavras dos seus usuários:
reply = desk.chat("what is in order 4411?", user="u-17")
desk.thumbs(reply, up=False) # one record: the turn's words, the tool, the router's verdict and rule
O registro guarda as palavras do turno avaliado e a leitura que o roteador fez delas -- a
ferramenta, o seu veredito e a regra que decidiu, as ferramentas que o agente podia alcançar -- e
nada mais: nenhum texto de resposta, valor de argumento, resultado de ferramenta ou turno
anterior. Um turno que o roteador não conseguiu ler e que o modelo respondeu registra o veredito
unread e a ferramenta que o modelo chamou. O mesmo polegar duas vezes é um único registro. Alguns
polegares são recusados com ThumbRefused, que diz o que avaliar no lugar: um turno com várias
instruções é avaliado um passo de cada vez (step=0, step=1, ...), e a resposta depois de uma
confirmação é avaliada no turno que nomeou a ferramenta. O stalwart serve recebe o mesmo polegar
em POST /threads/{id}/feedback, apenas da sessão a que a resposta foi mostrada.
As ferramentas de um servidor MCP são chamadas em uma sessão MCP por usuário: o user que um
turno nomeia, ou a sua conversa sob o stalwart serve. O que um servidor guarda para uma sessão
nunca chega a outro usuário. desk.close() encerra todas as sessões; o stalwart serve as encerra
quando para.
As palavras de uma ferramenta são simples: writes, undo, irreversible, retry_safe,
reads_outside, sends_out, after, not_after, at_most, e as palavras que as regras OWASP do
caçador de bugs leem (argument_kinds, sandboxed, confidential, ...). Um modelo é
provider:model, resolvido com a sua própria chave a partir do seu ambiente. Uma chave ausente é
nomeada antes de qualquer envio. Um modelo também pode ser uma função comum que recebe a conversa
e devolve a resposta.
| Comando | O que faz |
|---|---|
stalwart catalogue import openapi.yaml |
o catálogo a partir de um documento OpenAPI, ou das funções exportadas de um projeto TypeScript marcadas com @read ou @write, com as verificações de código executadas antes sobre os seus fontes; registrado com QLS_API_KEY |
stalwart rules compile rules.yaml [--save] |
a planilha de regras compilada e confrontada com o catálogo mais recente; --save a registra |
stalwart sources build|ask |
os documentos a partir dos quais os seus agentes respondem perguntas: build cria o índice deles aqui, a partir de arquivos locais, e ask --index responde uma pergunta a partir dele; os documentos nunca saem do seu host |
stalwart verify agent.yaml |
registra o catálogo com o seu rulebook, e faz o caçador de bugs do serviço caçar falhas no grafo que os agentes executam e no rulebook; sai com 1 quando o portão falha. --export-only FILE grava a exportação para a CI; --replay reproduz a testemunha de cada achado do grafo nos agentes, com cada ferramenta substituída por um stub, e reprova o portão por uma que não se reproduz |
stalwart serve agent.yaml |
executa os agentes sobre HTTP: turnos, confirmações retomadas em qualquer worker, o fluxo de passos, a reversão de um operador (com --operator-token), o polegar de uma pessoa em uma resposta (POST /threads/{id}/feedback) e a interface em / |
stalwart trace show|html|diff |
renderiza uma execução a partir dos seus spans, ou apenas a partir do armazenamento de execuções |
stalwart redteam agent.yaml --findings f.json |
campanhas de ataque contra o que o verify encontrou, com cada ferramenta substituída por um stub |
stalwart gateway --config gateway.yaml |
o gateway MCP: cada chamada de ferramenta que um agente faz, verificada no caminho até a ferramenta |
stalwart doctor |
a chave, o serviço e a sua latência, se o streaming atravessa a sua rede e se o runtime local ou o seu qls-router responde aos planos e às verificações, com a licença, o lease e a cota restante; sem chave, diz como obter uma |
stalwart check certificate.json |
um certificado verificado aqui, offline, pelo verificador de referência embarcado; não exige chave |
stalwart licence activate|request|answer|install|release|status |
a licença desta máquina, incluindo a ativação offline |
Nada na superfície do próprio stalwart_agents nomeia LangChain ou LangGraph: nenhum nome,
assinatura, erro ou mensagem. Um teste mantém o pacote assim. stalwart_agents.langchain existe
para uma equipe que já usa LangGraph. desk.graph() é a única forma de chegar ao grafo que roda por
baixo, para uma equipe que queira estendê-lo.
A mesma instalação traz o resto, cada parte documentada na sua própria página:
| Página | O que |
|---|---|
| Construa um agente | um agente seu: o cadastro, a chave, a licença, um agent.yaml, um turno, uma confirmação e o stalwart serve |
| Exemplos completos | cada capacidade mostrada: o modelo chamado só onde você permite, ou nunca; reversões; recibos atrás do Kong; um grafo LangGraph validado; um certificado para cada regra; cada rascunho verificado |
| O cliente | stalwart_agents.client: a API da Stalwart a partir do Python -- roteamento, verificações de ferramentas, verificação de guardrails, de grafos de agentes e de random forests, e o caçador de bugs |
| Início rápido do cliente | o cliente em cinco minutos |
| LangChain e LangGraph | stalwart_agents.langchain: os middlewares, as partes do grafo e o Guard, para uma equipe que já usa LangGraph |
| O gateway | stalwart gateway: o gateway MCP, o seu contêiner e a sua política de rede |
| A receita do Kong | a verificação na frente das suas ferramentas a partir do Kong AI Gateway, com cada arquivo que a receita aplica |
| A licença e o runtime | o que é uma licença, o que funciona sem ela e o runtime que o wheel traz |
| A referência da API Python (em inglês) | cada nome público de stalwart_agents, .client, .langchain e .gateway, a partir das suas docstrings |
| A referência da API HTTP | a API Stalwart Agents que o cliente chama |