stalwart_agents.langchain
stalwart_agents.langchain: Stalwart's routing, tool check and sagas inside LangChain and LangGraph agents.
stalwart_agents.langchain.middleware:StalwartRouterMiddlewareandStalwartToolGuardMiddlewareforcreate_agent, andregister_catalogue, the tool importer.stalwart_agents.langchain.callbacks:StalwartFeedbackCallback, a person's thumb as feedback.stalwart_agents.langchain.graph: the graph parts --StalwartState,stalwart_messages, the router node, the guarded tool node, declared-transition edges, the rollback and rewind,create_stalwart_agent-- andexport_graphandreplay_witness.stalwart_agents.langchain.guard:Guard, the same decisions for plain Python, with no graph and no LangChain import.stalwart_agents.langchain.server: confirmations resumed on any worker, streamed, and approved out of band.stalwart_agents.langchain.traceandstalwart_agents.langchain.otel: a run's spans, rendered locally.stalwart_agents.langchain.redteam: the attacker campaigns.stalwart_agents.langchain.nemo: the NeMo Guardrails actions and rails (the[nemo]extra).
Everything here runs in the team's environment. No selector, vocabulary or check is in this package: every decision it acts on is made by the runtime on the same machine, or by the router the customer runs where the runtime is absent.
Names are loaded on first use, so from stalwart_agents.langchain.guard import Guard loads
neither LangChain nor LangGraph.
One call a stub recorded: the tool, its arguments, its place in the run, and whether a rollback made it as an undo.
Example:
Call("refund_card", {"order_id": "7"}, 0) Call(tool='refund_card', arguments={'order_id': '7'}, index=0, undo=False)
Routing, the tool check and sagas for plain Python.
Args:
client: a stalwart_agents.client.Client; it answers through the licensed runtime, or
through the enterprise qls-router it names.
agent: the agent the calls are made for, as the catalogue names it.
catalogue: the extracted-model catalogue; read for each tool's undo, retry safety
and retryable errors. The check itself reads the tenant's own.
tools: the tools a compensation may call, by name; a tool passed to run.call is
added on first use.
on_confirm: answers a confirmation with approve, skip or cancel_all; where
not given, run.call raises ConfirmationRequired and the run pauses.
on_unknown: answers an unknown outcome with happened or did_not_happen; where
not given, OutcomeUnknown is raised and nothing is rolled back.
on_stuck: answers a stuck rollback with retry, done_by_hand or abandon;
where not given, SagaStuck is raised with the report.
atomic: roll back on a terminal failure; False stops and keeps the stack.
tracer: where spans go; none are kept by default.
sleep: waits between an undo's tries; a test passes one that does not wait.
Example:
guard = Guard(None, agent="desk", catalogue={"tools": []}) guard.atomic True
One call checked with the session's history; nothing runs. The SDK's ToolCheck.
Args:
tool: the tool, or its name.
arguments: the call's arguments.
session: the conversation, from session.
origins: where each argument came from, by name; the model's where not given.
labels: the labels the call carries.
context: the host's context values for the gate.
confirmation: a person's approval of exactly these arguments, where there was one.
Returns:
The SDK's ToolCheck: allow, confirm or refuse, with the reasons.
A saga over @p session: calls made through the Run it yields are undone together on a terminal failure.
Args:
session: the conversation, from session.
stack: the compensation stack of a run paused at a confirmation, to carry on with.
Returns:
A context manager that yields the Run the block makes its calls through.
Raises:
Whatever ended the run, after an atomic run's rollback: CallRefused,
ToolFailed, RunCancelled, or the block's own exception. A rollback that got
stuck raises SagaStuck instead, from the original failure.
Answers every call with what its function returns for the conversation so far.
The offline stand-in for a team's model in a campaign: a function of what the model was shown, so a suite can say exactly how the model treats planted text.
Example:
model = RespondingChatModel(respond=lambda messages: AIMessage(content=f"{len(messages)} message(s)")) model.invoke("hello").content '1 message(s)'
Returns its script's messages in order, whatever it is asked.
Each entry is a message, or a function of the conversation so far that returns one, for a model whose answer depends on what a tool returned.
Example:
model = ScriptedChatModel(script=[tool_call("get_order", {"order_id": "7"}, "call-1")]) model.invoke("where is order 7?").tool_calls[0]["name"] 'get_order' model.invoke("and now?").content 'done' model.turns 2
Remembers each run's turn and tool, and posts a thumb on it.
Args:
client: a stalwart_agents.client.Client.
agent: the agent the runs are for.
Example:
callback = StalwartFeedbackCallback(None, agent="desk") callback.runs {}
Remembers the run's turn: the last user message of a root run's input.
Args:
serialized: the chain's serialised form; not read.
inputs: the chain's input.
run_id: the run's id.
parent_run_id: the run it is part of; None for a root run.
kwargs: what LangChain passes besides; not read.
Keeps the router's reading of a root run's turn, from the state the run ended with.
Nothing here raises: a state it cannot read leaves the run's record as the turn and tool alone.
Args:
outputs: the chain's output: a graph's state for a root run.
run_id: the run's id.
parent_run_id: the run it is part of; None for a root run.
kwargs: what LangChain passes besides; not read.
Remembers the turn from the model's messages, where the run's input did not carry it.
Args: serialized: the model's serialised form; not read. messages: the messages the model is shown. run_id: the run's id. parent_run_id: the run it is part of. kwargs: what LangChain passes besides; not read.
Remembers the tool the run called.
Args:
serialized: the tool's serialised form; its name is read.
input_str: the tool's input; not read.
run_id: the run's id.
parent_run_id: the run it is part of.
kwargs: what LangChain passes besides; name is read where @p serialized has none.
The feedback record for @p run_id, as /v1/feedback takes it.
Args:
run_id: the root run rated.
up: a thumb up; False for a thumb down.
corrected: the person's correction, where they gave one.
step: the step rated, counting from 0, where the router read several instructions in the turn.
Returns: The record: the agent, the rated turn's words, the thumb and the tool; and, where a router read the turn, its verdict, deciding rule and the agent's visible tools.
Raises: KeyError: the callback saw no run with this id. ThumbRefused: a thumb the toolkit refuses, naming what to thumb instead.
Posts the person's thumb on @p run_id: one record; a retry of the call is one record too.
Args:
run_id: the root run rated.
up: a thumb up; False for a thumb down.
corrected: the person's correction, where they gave one.
step: the step rated, counting from 0, where the router read several instructions in the turn.
Returns: The service's answer to the posted record.
Raises: KeyError: the callback saw no run with this id. ThumbRefused: a thumb the toolkit refuses; nothing is posted.
Routes the latest user turn before the model is called. See the module's table.
Args:
client: a stalwart_agents.client.Client: plans come from the licensed runtime, or the qls-router it names.
agent: the agent the turns are read for.
catalogue: the catalogue to plan against: an extracted model, or a registered version.
on_unread: for a turn the router cannot read, once it asked the user to rephrase it: refuse; model
to let the model answer it; or ask to let the model answer it once the user says yes.
Reads: messages, stalwart.session, stalwart.history, stalwart.plan.
Writes: stalwart.session, stalwart.history, stalwart.plan (the plan, where it is, each call's argument origins,
and rated: what a thumb on the reply records).
Example:
router = StalwartRouterMiddleware(None, agent="desk") router.on_unread 'refuse'
The schema for state passed to the middleware nodes.
A new user turn read into a plan, kept in stalwart.plan; nothing on any other model call.
Args: state: the agent's state. runtime: LangGraph's runtime; not read.
Returns:
The state update with the plan, the session and the history; None where the last
message is not a user turn.
The plan's step in place of the model call, or the model call where the plan hands the turn on.
Args: request: the model call LangChain is about to make. handler: makes the model call.
Returns: The plan's step as the model's response, with the plan's progress as a state update; or the model's own response.
Checks every tool call before it runs, whoever chose it.
Args:
client: a stalwart_agents.client.Client.
agent: the agent the calls are made for.
catalogue: the extracted-model catalogue, for the confirmation's irreversible warning.
context: the host's context values for the gate, or a function of the state.
Reads: stalwart.session, stalwart.history, stalwart.labels, stalwart.plan (the router's origins).
Writes: messages (the call's result), stalwart.session, stalwart.history, stalwart.labels.
Example:
guard = StalwartToolGuardMiddleware(None, agent="desk") guard.agent 'desk'
The schema for state passed to the middleware nodes.
Check, then run through @p handler only on allow; a refusal runs nothing.
Args: request: the tool call LangChain is about to run. handler: runs it.
Returns:
A Command with the call's result, or the refusal in one sentence, and the session's update.
The catalogue's tools as stubs, with the calls they recorded and the text planted in them.
Example:
stubs = StubTools({"tools": [{"name": "get_order"}, {"name": "refund_card"}]}) stubs.as_list(["get_order"])[0].invoke({}) 'fixture data' stubs.called("get_order"), stubs.called("refund_card") (True, False)
The catalogue's entry for @p name.
Returns: The tool's entry, in the extracted-model format.
The stubs, all of them or those named, in catalogue order.
Args: names: the tools to take; every one where not given.
Returns: The stubs, as LangChain tools.
Put @p text in @p tool's stubbed output from its next call on.
Raises: KeyError: @p tool is not in the catalogue.
Whether @p tool's stub ran.
Returns: True where it recorded a call.
The package's version: 1.0.
Arguments for a call to @p tool: each declared property, filled by its declared kind.
@p planted, where given, goes into every string argument instead, which is how a campaign makes a call carry the text it planted.
Returns: The arguments, by name.
Example:
tool = {"name": "run_code", "input_schema": {"properties": {"source": {"type": "string"}}}, ... "argument_kinds": {"source": "code"}} arguments_for(tool) {'source': "print('witness')"} arguments_for(tool, planted="qls-r1") {'source': 'qls-r1'}
The idempotency key of the write being made, inside a tool a guarded call runs; None outside one.
A tool that calls an API honouring idempotency keys passes it on, in the header the
catalogue's idempotency: {header: ...} names.
Returns:
The key, or None outside a guarded call.
Example:
current_idempotency_key() is None True
The smallest value of a JSON schema's type: what a stub returns for it.
Args:
schema: the JSON schema; None or empty for a tool that declares none.
Returns:
The value: the first of an enum, an object of its properties' values, an empty
list, zero, False, None, or FIXTURE_LINE for a string or no schema.
Example:
fixture_value({"type": "object", "properties": {"id": {"type": "integer"}, "note": {"type": "string"}}}) {'id': 0, 'note': 'fixture data'} fixture_value(None) 'fixture data'
Registers @p tools as the tenant's catalogue: a new version, linted (POST /v1/catalogues).
Args:
tools: the agent's LangChain tools.
agent: the agent they are for.
client: a stalwart_agents.client.Client.
agents: every agent and the tools each may reach, where more than one shares the catalogue.
options: the catalogue's own declarations, as register_catalogue on the client takes them
(stores, budget, modules, rulebook_ir).
Returns:
The SDK's CatalogueVersion: its version, digest and lint report.
Example:
class Client: ... def register_catalogue(self, definitions, **options): ... return [d["name"] for d in definitions], options["source"] register_catalogue([{"name": "get_order"}], agent="desk", client=Client()) (['get_order'], 'langchain')
A model turn that calls one tool.
Args: name: the tool's name. arguments: the call's arguments. call_id: the call's id, which the tool's result answers.
Returns: The model's message, with the one tool call.
Example:
tool_call("get_order", {"order_id": "7"}, "call-1").tool_calls [{'name': 'get_order', 'args': {'order_id': '7'}, 'id': 'call-1', 'type': 'tool_call'}]
The tools as LangChain tool definitions: name, description, argument schema and metadata.
Args:
tools: LangChain tools (BaseTool), or definitions already in that shape.
Returns: One definition per tool, in the order given.
Example:
from langchain_core.tools import tool @tool ... def get_order(order_id: str) -> str: ... '''Look an order up.''' ... return order_id tool_definitions([get_order])[0]["name"] 'get_order'