Skip to main content

stalwart-agents

Build your own AI agent, powerful but controlled: your tools and your rules, checked on your own machine. Declare it in one file -- its tools, its rules and its users -- then check it and run it. You don't need to learn an agent framework for any of this.

Before you start. You need a Stalwart account and its API key. Sign up at stalwart.it: a free account opens with its first key, in the console, in English or Portuguese. Set the key as QLS_API_KEY, and run stalwart licence activate once on each machine that runs your agents. What each plan includes is on stalwart.it. Nothing in the package works without the key and an activated licence, except activating the licence itself, stalwart doctor, which says what is missing, and stalwart check, which checks a certificate offline (the licence and the 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

The documentation is at api.stalwart.it/sdk, in English and Portuguese. Start with Build an agent: an agent of your own, from sign-up to stalwart serve.

The local runtime ships for Linux (x86-64 and arm64), macOS on Apple silicon and Windows (x86-64). It reads every turn and checks every tool call on your own machine, under your licence. Anywhere else -- an Intel Mac, Alpine, Windows on ARM -- the same command installs the package without it, and the package's other parts work as usual. Stalwart's hosted API answers no plan and no check: with no runtime, a plan or a check raises SelectorUnavailableError, saying how to set the runtime up.

# 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

Or in Python, with the same meaning:

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")

Before a turn runs, the agents' graph is proved once (verify=False runs it unproved). A turn nothing reads is asked to be said again once; then on_unread decides: refuse, model, ask, where the person says whether the model may have it (desk.confirm(user, "approve") or "decline"), or answer, from your documents (below). A request no tool covers -- a calculation, say -- is what model is for. With refuse, no hands_off_to, composer: template for replies and composer: extract for answers, no model is ever called (worked examples).

Guarded and proved. Two checks sit around the model, both decided by native code on your own host: the router decides whether a model is called at all, and every tool call is checked -- allow, confirm or refuse -- whether the router or the model chose it. Before the first turn the graph is proved: every path to a write without its check, its confirmation or its order rule is a finding, and Agent(...) raises NotVerified with nothing run. The rulebook it is proved against is the one it runs under: at start-up, and on every stalwart verify, it is registered with the catalogue for the tool check to enforce (RulebookNotHeld where it cannot be).

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:            # nothing ran
    for finding in refused.findings:
        print(finding["rule"], finding.get("action"))   # QG012 issue_refund
$ stalwart verify agent.yaml --replay    # in CI: exits 1 when the gate fails
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

In CI. stalwart verify runs the code checks on your machine and the bug hunter on Stalwart's servers -- nothing besides the package is installed. A runner activates its licence first and releases it last:

    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

Exit 0 is a passed gate, 1 a failed one, 2 a file it cannot read or no key, and 3 a hunt that could not run -- never a clean result (the command in full).

Every check, every guarantee and what is proved, with examples: Guarded and proved.

Rules without Colang. The rules over your tools can be a YAML sheet a person reads aloud, compiled to the same rulebook a Colang policy becomes:

# 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 compiles it and lints it against your newest catalogue -- a rule for an action the catalogue lacks, a write the sheet never names -- and --save registers it on the catalogue, where every tool check enforces it. A construct the compiler cannot state exactly is refused with its line, never approximated.

Replies true to what was done. replies: holds every reply to the run it follows:

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

Each sentence that speaks of a write is held to that write's outcome -- done only if it ran, a failed or refused one never told as done -- and every amount, date, identifier or address in it to a value the call was sent or returned. A sentence that misstates is replaced by the outcome's phrasing, and a write the reply leaves out is added. Personal data the rules withhold is masked, or the reply held back. It runs in the local runtime, and only counts leave the machine.

Answers from your documents. A question no tool answers can be answered from your own documents, never by a tool's side effect: documentation describing a refund is never a refund.

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

Each sentence cites the passage it draws on, and the support check removes one that states a number, date, amount, name or negation its passages do not. It checks those facts, not the meaning around them: a sentence that keeps a passage's facts and draws a conclusion the passage does not is not caught, which is why extract -- the passages' own sentences -- is the default. compose needs a plan that includes composed answers.

Calculations on your host. A turn that needs a calculation no tool answers -- what is the average price of my orders -- is a compute step, on every plan: the tools it reads run, your own model writes one Python function from the data's shape (never its values), the runtime admits it -- no socket, no file, no import outside a short list -- and the qls-sandbox image runs it in a fresh container with no network. It needs a container engine: python -m stalwart_agents.sandbox fetches the image this release runs, and without the engine or the image the turn is declined, its reason starting compute off:.

What reaches Stalwart's servers. Conversations, tool calls, a calculation's data, function and result, your documents and their index stay on your host. What is sent -- a solve and its inputs, a catalogue's tool descriptions, a rule sheet, a guardrail generation prompt, and a thumb's feedback record, which carries the words of the turn it rates -- is scanned first for e-mail addresses, telephone numbers, payment card numbers, IBANs, UK National Insurance and Brazilian CPF numbers, and any identifiers you list as your own (Client(own_identifiers=[...])). A match sends nothing: PersonalDataError names each match's kind and where it is, never its value.

A person's thumb on a reply is feedback the hosted miner learns your users' words from:

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

The record holds the rated turn's words and the router's reading of them -- the tool, its verdict and deciding rule, the tools the agent could reach -- and nothing else: no reply text, argument value, tool result or earlier turn. A turn the router could not read and the model answered records the verdict unread and the tool the model called. The same thumb twice is one record. Some thumbs are refused with ThumbRefused, saying what to thumb instead: a turn of several instructions is thumbed one step at a time (step=0, step=1, ...), and the reply after a confirmation is thumbed on the turn that named the tool. stalwart serve takes the same thumb at POST /threads/{id}/feedback, from the session that was shown the reply only.

An MCP server's tools are called in an MCP session per user: the user a turn names, or its thread under stalwart serve. What a server keeps for one session never reaches another user. desk.close() ends every session; stalwart serve ends them when it stops.

A tool's words are plain: writes, undo, irreversible, retry_safe, reads_outside, sends_out, after, not_after, at_most, and the words the bug hunter's OWASP rules read (argument_kinds, sandboxed, confidential, ...). checks, filter, spend, currency and outcome say only if, how much and when is it done in one grammar (stalwart_agents.conditions); they are checked when agent.yaml is read, and not yet applied to a call. A model is provider:model, resolved with your own key from your environment. A missing key is named before anything is sent. A model can also be a plain function that takes the conversation and returns the reply.

Command Does
stalwart catalogue import openapi.yaml the catalogue from an OpenAPI document, or from a TypeScript project's exported functions tagged @read or @write, with the code checks run over its sources first; registered with QLS_API_KEY
stalwart rules compile rules.yaml [--save] the rule sheet compiled and linted against the newest catalogue; --save registers it
stalwart sources build|ask the documents your agents answer questions from: build makes their index here, from local files, and ask --index answers a question from it; the documents never leave your host
stalwart verify agent.yaml registers the catalogue with its rulebook, and has the service's bug hunter hunt the graph the agents run and the rulebook; exits 1 when the gate fails. --export-only FILE writes the export for CI; --replay replays each graph finding's witness on the agents, every tool a stub, and fails the gate on one that does not reproduce
stalwart serve agent.yaml runs the agents over HTTP: turns, confirmations resumed on any worker, the step stream, an operator's rollback (with --operator-token or QLS_OPERATOR_TOKEN; 403 without), a person's thumb on a reply (POST /threads/{id}/feedback), and the interface at /
stalwart trace show|html|diff renders a run from its spans, or from the run store alone
stalwart redteam agent.yaml --findings f.json attacker campaigns at what verify found, every tool a stub
stalwart gateway --config gateway.yaml the MCP gateway: every tool call an agent makes, checked on its way to the tool
stalwart doctor the key, the service and its latency, whether streaming gets through your network, and whether the local runtime answers plans and checks, with the licence, the lease and the quota left; with no key, it says how to get one
stalwart check certificate.json a certificate checked here, offline, by the carried reference checker; needs no key
stalwart licence activate|request|answer|install|release|status this machine's licence, offline activation included

Nothing on stalwart_agents' own surface names LangChain or LangGraph: no name, signature, error or message. A test holds the package to that. stalwart_agents.langchain is there for a team already on LangGraph. desk.graph() is the one way to reach the graph that runs underneath, for a team that wants to extend it.

The same install carries the rest, each documented on its own page:

Page What
Build an agent an agent of your own: sign-up, the key, the licence, an agent.yaml, a turn, a confirmation and stalwart serve
Worked examples each capability shown: the model called only where you allow it, or never; rollbacks; receipts behind Kong; a LangGraph graph validated; a certificate for every rule; every draft verified
The client stalwart_agents.client: the Stalwart API from Python -- routing, tool checks, guardrail, agent graph and random forest verification, and the bug hunter
The client's quickstart the client in five minutes
LangChain and LangGraph stalwart_agents.langchain: the middlewares, the graph parts and Guard, for a team already on LangGraph
The gateway stalwart gateway: the MCP gateway, its container and network policy
The Kong recipe the check in front of your tools from Kong AI Gateway, with every file the recipe applies
The licence and the runtime what a licence is, what works without one, and the runtime the wheel carries
The Python API reference every public name of stalwart_agents, .client, .langchain and .gateway, from their docstrings
The HTTP API reference the Stalwart Agents API the client calls

Metadata

Release files for stalwart-agents 1.0.60

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for stalwart-agents 1.0.60
File
stalwart_agents-1.0.60-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details
stalwart_agents-1.0.60-py3-none-manylinux_2_28_x86_64.whl Python 3 none Linux glibc 2.28+ x86-64 Details
stalwart_agents-1.0.60-py3-none-manylinux_2_28_aarch64.whl Python 3 none Linux glibc 2.28+ ARM64 Details
stalwart_agents-1.0.60-py3-none-macosx_12_0_arm64.whl Python 3 none macOS 12.0+ ARM64 Details
stalwart_agents-1.0.60-py3-none-any.whl Python 3 none any Details

Total release size: 51.8 MB

Release files / stalwart_agents-1.0.60-py3-none-win_amd64.whl

Download URL stalwart_agents-1.0.60-py3-none-win_amd64.whl
Size 10.2 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
2499dd034a0248dc1ac548a847a9fc9a81ac77f29d5b983ce25117f188e831b1
BLAKE2b-256 checksum
How to use checksums
064691e5d068fa931f84107e4b8a2299819f0d67ed877416f9a175030cf7cd53
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Release files / stalwart_agents-1.0.60-py3-none-manylinux_2_28_x86_64.whl

Download URL stalwart_agents-1.0.60-py3-none-manylinux_2_28_x86_64.whl
Size 15.8 MB
Tags Linux glibc 2.28+ x86-64 Python 3
SHA-256 checksum
How to use checksums
991e63cde1695db56357a8aba25860b9fef6c797a59b1693bc25f503671352d3
BLAKE2b-256 checksum
How to use checksums
0d9f8a238a359158b05607d3a0903c8b0f1e2bfc7489c450451319ac4b0c635a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Release files / stalwart_agents-1.0.60-py3-none-manylinux_2_28_aarch64.whl

Download URL stalwart_agents-1.0.60-py3-none-manylinux_2_28_aarch64.whl
Size 15.3 MB
Tags Linux glibc 2.28+ ARM64 Python 3
SHA-256 checksum
How to use checksums
5d85e3da4d1521b237503374abfaf327078aa4014bed93dc8ea3d04e274eb7c5
BLAKE2b-256 checksum
How to use checksums
25bf5e89c0425e0d37013a750a2f28498cdeac1498869a93e895e9498612452c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Release files / stalwart_agents-1.0.60-py3-none-macosx_12_0_arm64.whl

Download URL stalwart_agents-1.0.60-py3-none-macosx_12_0_arm64.whl
Size 9.9 MB
Tags Python 3 macOS 12.0+ ARM64
SHA-256 checksum
How to use checksums
bc64151ba4490d55c83afc5efa2c8267e326c19068205cefa604db79e47c7861
BLAKE2b-256 checksum
How to use checksums
49f250cf6c0f77d761b4037c60e032276dff1258ca411c362b895e5c9d6d604d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Release files / stalwart_agents-1.0.60-py3-none-any.whl

Download URL stalwart_agents-1.0.60-py3-none-any.whl
Size 635.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b03a33e363c39e595e63ba94a3684c8347ac8f5e5a856b2ebadb5108b4b35250
BLAKE2b-256 checksum
How to use checksums
e7eafbbbcf959705529778c5020cb2cd6e7eb3d196c67a6eab6b0ea02732bf2e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

Security for stalwart-agents 1.0.60

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 5 files in this release have been uploaded with provenance metadata.

Select from 5 files

Attestation for stalwart_agents-1.0.60-py3-none-win_amd64.whl

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page