Metadata-Version: 2.5
Name: stalwart-agents
Version: 1.0.45
Summary: Declare an agent in one file -- its tools, its rules, its users -- check it before it runs, and run it: every turn routed, every tool call checked, a failed run undone.
Project-URL: Homepage, https://stalwart.it
Project-URL: Documentation, https://api.stalwart.it/
Author-email: Stalwart Technologies Ltd <crm@stalwart.it>
License-Expression: LicenseRef-Stalwart-Proprietary AND MIT
License-File: LICENSE
License-File: src/stalwart_agents/client/_vendor/LICENSE
Keywords: agents,ai,guardrails,langchain,langgraph,mcp,red-team,tool-calling,verification
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Requires-Python: <3.15,>=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: langchain-core<2,>=1
Requires-Dist: langchain<2,>=1
Requires-Dist: langgraph<2,>=1
Requires-Dist: pyyaml>=6
Requires-Dist: uvicorn>=0.30
Provides-Extra: all
Requires-Dist: langchain-anthropic<2,>=1; extra == 'all'
Requires-Dist: langchain-google-genai<5,>=4; extra == 'all'
Requires-Dist: langchain-mistralai<2,>=1; extra == 'all'
Requires-Dist: langchain-ollama<2,>=1; extra == 'all'
Requires-Dist: langchain-openai<2,>=1; extra == 'all'
Provides-Extra: anthropic
Requires-Dist: langchain-anthropic<2,>=1; extra == 'anthropic'
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: jsonschema>=4; extra == 'dev'
Requires-Dist: langgraph-checkpoint-sqlite<4,>=3.1; extra == 'dev'
Requires-Dist: langgraph-supervisor<0.1,>=0.0.31; extra == 'dev'
Requires-Dist: langgraph-swarm<0.2,>=0.1; extra == 'dev'
Requires-Dist: lupa<3,>=2.5; extra == 'dev'
Requires-Dist: mcp<3,>=2.3; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: opentelemetry-api>=1.25; extra == 'dev'
Requires-Dist: opentelemetry-sdk>=1.25; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Requires-Dist: twine>=5; extra == 'dev'
Requires-Dist: types-pyyaml; extra == 'dev'
Provides-Extra: google
Requires-Dist: langchain-google-genai<5,>=4; extra == 'google'
Provides-Extra: mistral
Requires-Dist: langchain-mistralai<2,>=1; extra == 'mistral'
Provides-Extra: nemo
Requires-Dist: nemoguardrails<0.25,>=0.24; (python_version < '3.14') and extra == 'nemo'
Provides-Extra: ollama
Requires-Dist: langchain-ollama<2,>=1; extra == 'ollama'
Provides-Extra: openai
Requires-Dist: langchain-openai<2,>=1; extra == 'openai'
Provides-Extra: otel
Requires-Dist: opentelemetry-api>=1.25; extra == 'otel'
Requires-Dist: opentelemetry-sdk>=1.25; extra == 'otel'
Provides-Extra: postgres
Requires-Dist: langgraph-checkpoint-postgres<4,>=3.1; extra == 'postgres'
Provides-Extra: sqlite
Requires-Dist: langgraph-checkpoint-sqlite<4,>=3.1; extra == 'sqlite'
Description-Content-Type: text/markdown

# stalwart-agents

Declare an agent 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.

```
$ 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 local runtime ships for Linux (x86-64 and arm64), macOS on Apple silicon and Windows (x86-64).
Anywhere else -- an Intel Mac, Alpine, Windows on ARM -- the same command installs the package
without it, and the Stalwart service answers its plans and checks.

```yaml
# 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:

```python
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`, or `ask`, where the person says whether the model may have it
(`desk.confirm(user, "approve")` or `"decline"`).

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

```python
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`, ...).
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 verify agent.yaml` | registers the catalogue, verifies the graph the agents run and hunts 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, 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 ([the gateway](docs/gateway.md)) |
| `stalwart doctor` | the key, the service and its latency, whether streaming gets through your network, and whether the local runtime or the service answers, with the licence, the lease and the quota left |
| `stalwart check certificate.json` | a certificate checked here, offline, by the carried reference checker |
| `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 |
|---|---|
| [The client](docs/client.md) | `stalwart_agents.client`: the Stalwart API from Python -- routing, tool checks, guardrail, agent graph and random forest verification, and the bug hunter |
| [LangChain and LangGraph](docs/langchain.md) | `stalwart_agents.langchain`: the middlewares, the graph parts and `Guard`, for a team already on LangGraph |
| [The gateway](docs/gateway.md) | `stalwart gateway`: the MCP gateway, its container and network policy, and [the Kong recipe](recipes/kong/README.md) |
| [The runtime](docs/runtime.md) | the compiled runtime the wheel carries, and its licence |
| [Quickstart](docs/quickstart.md) | the client in five minutes |
