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