LangChain and LangGraph

stalwart_agents.langchain puts Stalwart's routing, tool check and sagas inside LangChain and LangGraph agents. No selector, vocabulary or check is in this module. Every decision it acts on is made by the runtime the wheel carries, on the same machine under your licence, or by the enterprise qls-router you run in your own environment, where the client names it (router_url). Stalwart's hosted API makes none of them. A turn that needs a calculation no tool answers is run by the compute node in the qls-sandbox container image on your own host, where a container engine and the image are present; elsewhere it is declined, its reason starting compute off:.

$ pip install stalwart-agents                # the middlewares, the graph parts, Guard
$ pip install "stalwart-agents[otel]"        # OpenTelemetry spans
$ pip install "stalwart-agents[postgres]"    # resume confirmations from Postgres (or [sqlite])
$ pip install "stalwart-agents[nemo]"        # the NeMo Guardrails actions and rails

LangChain: two middlewares, an importer, a callback

from langchain.agents import create_agent
from stalwart_agents.langchain import (StalwartRouterMiddleware, StalwartToolGuardMiddleware,
                                StalwartFeedbackCallback, register_catalogue)

register_catalogue(tools, agent="desk", client=client)            # POST /v1/catalogues
agent = create_agent(model, tools, middleware=[
    StalwartRouterMiddleware(client, agent="desk", on_unread="refuse"),
    StalwartToolGuardMiddleware(client, agent="desk"),
])

LangGraph: the parts, or the whole graph

from stalwart_agents.langchain.graph import create_stalwart_agent

agent = create_stalwart_agent(model, tools, catalogue=catalogue, agent="desk", client=client,
                              checkpointer=checkpointer)            # verified once per graph digest
Part Does
StalwartState our channels beside your messages: the session, the history (append-only), trust labels, the saga, open confirmations, the plan
stalwart_messages, trim_pairs add_messages, and a tool call never left without its result; results in call order; trimming that keeps pairs
stalwart_route_node the turn read into a plan, returned as Command with its destinations declared
StalwartToolNode every call checked (freeze, check, run once, record, push the undo), one step a pass, so a crash mid-call is resumed honestly
stalwart_edge a conditional edge that can take only the destinations it declares, and a rejected_route otherwise
stalwart_rollback_node, stalwart_rewind_node a failed run undone last-in first-out through the check, then the memory rewound, never with an undo pending and never the history
stalwart_hand_off_node a hand-off that carries the handing agent's label
stalwart_branches, stalwart_join_node a message's calls fanned out as Send branches, checked one history at a time and undone in reverse order of completion; a subgraph's undos handed to its parent when it completes, its failure the parent's when it does not
with_stalwart_channels our channels added to any state schema, for a supervisor, a swarm or your own graph that runs a stalwart agent across turns
create_stalwart_agent, build_stalwart_graph the graph generated from your catalogue, verified by the graph mode; compile() verifies whatever nodes you add
validate_state, validating each break of the state named, the history's chain token too with history_verifier; debug mode stops at the node that caused it
export_graph, replay_witness the export the verifier reads, and a finding's witness replayed with every tool stubbed

Plain Python: Guard

from stalwart_agents.langchain.guard import Guard

guard = Guard(client, agent="desk", catalogue=catalogue, tools=[charge_card, refund_card])
with guard.run(guard.session()) as run:
    run.call(charge_card, order_id=o, amount=a)    # checked, keyed, its undo pushed
    run.call(provision_account, order_id=o)        # raises -> the charge refunded

stalwart_agents.langchain.guard loads no LangChain and no LangGraph.

Around the agent