langchain-right-rudder
A coherence read for LangChain agents, as an agent middleware.
Fathom is now Right Rudder, by Embedded Risk Analytics.
pip install langchain-right-rudderreplaceslangchain-fathom, and the old class names, state keys and imports keep working in this release.
A long-running agent loses coherence with its own decisions. It renames a field at one step, then writes the old name at a later one. It marks a record done that it never wrote. The run reports success, and the contradiction ships. langchain-right-rudder watches the agent run and, when it finishes, names the step where a later action contradicts an earlier commitment.
The middleware observes the agent and does not change what it does. It maps the tool calls in the message history to a committed-state op stream, sends that stream to the Right Rudder read, and reports the findings. The read is deterministic and needs no model access.
Install
pip install langchain-right-rudder
Use
from langchain.agents import create_agent
from langchain_right_rudder import RightRudderMiddleware
agent = create_agent(
model="gpt-5.5",
tools=[...],
middleware=[RightRudderMiddleware()],
)
result = agent.invoke({"messages": [...]})
By default the middleware logs any findings through the right-rudder logger. Two other modes fit a test suite or a pipeline.
RightRudderMiddleware(on_finding="raise") # raise RightRudderCoherenceError when a run is not coherent
RightRudderMiddleware(on_finding="store") # put the verdict on agent state under the "fathom" key
Under store, the verdict arrives on the result.
result = agent.invoke({"messages": [...]})
verdict = result["fathom"]
verdict["coherent"], verdict["findings"]
The middleware declares that key in its own state schema, so a graph that carries a state schema of its own keeps the verdict rather than dropping it. Version 0.1.0 declared nothing, and an orchestrator such as a deepagents agent dropped the verdict before the caller saw it.
If the run is a rename or a migration, tell the read which token replaced which, so it also reports records left on the old value at the end.
RightRudderMiddleware(supersede=[("guest_id", "customer_id")])
What it finds
| Finding | The agent... |
|---|---|
stale_reference |
acts on a record it already removed or renamed away |
superseded_value |
writes a value it already replaced |
residual |
ends the run with a record still carrying a value it replaced elsewhere |
duplicate_commit |
adds an entity a collection already holds |
post_commit_mutation |
changes a record after committing it |
A coherent run returns coherent and the middleware reports nothing else.
Agents that delegate
The middleware reads the calls of the agent it rides. An orchestrator that hands work to sub-agents runs each sub-agent as its own agent with its own message history, so an orchestrator-level middleware sees the delegation and the orchestrator's own tools, and not what the sub-agents did. Give each sub-agent its own RightRudderMiddleware to cover the whole run. Or attach one RightRudderCapture at the graph root, which follows the orchestrator into its sub-agents and returns the whole run in a single trace, the same trace the right-rudder deepagents adapter reads. On one deepagents research run we measured, the orchestrator's middleware alone returned 3 of the 9 findings a trace across every sub-agent returned.
Your own tool names
The read knows common state-writing tool names. For tools with your own names, map them once and pass the file.
RightRudderMiddleware(mapping_path="tools.json")
{"save_decision": {"op": "set", "kind": "decision", "key": "topic", "value": "text"},
"confirm_booking": {"op": "commit", "kind": "flight", "key": "booking"}}
The repair
RightRudderMiddleware watches. RightRudderRepairMiddleware acts. It sits between the model and its tools, sends the actions the model just proposed together with what the run has already committed, and gets back one of three decisions.
from langchain_right_rudder import RightRudderRepairMiddleware
agent = create_agent(
model="gpt-5.5",
tools=[...],
middleware=[RightRudderRepairMiddleware()],
)
On proceed every proposed action is consistent with what the agent already committed and nothing changes. On filter the agent keeps its own consistent alternatives and the contradicting calls never reach the tools. On reground none of them survive, so the committed facts go back in front of the model as a system-role message and the model is asked again, once by default.
The repair needs a free key. Get one with right-rudder key you@example.com from the right-rudder package and set RIGHT_RUDDER_API_KEY, or pass key=. The middleware checks for it when you construct it, so a run fails at setup rather than twenty steps in. The two reads stay free without a key.
This middleware changes what your agent does, which is why it carries its own class rather than a flag on the read. Two settings cover the cases where that matters.
RightRudderRepairMiddleware(on_error="raise") # stop the run rather than skip the repair
RightRudderRepairMiddleware(max_reasks=0) # never re-ask; filter and proceed only
on_error defaults to "proceed", so an unreachable service or a spent daily limit logs a warning and lets the agent act on its own proposal. A coherence repair should not take down a running agent. Pass "raise" when you are measuring a before-and-after, since a silently skipped repair would contaminate the result.
Every repaired step appends an entry to agent state under fathom_repair, carrying the decision, the proposals evaluated, the finding kinds of anything dropped, and the re-asks spent. That is the log a run needs to report what the repair did.
result = agent.invoke({"messages": [...]})
for step in result["fathom_repair"]:
print(step["decision"], step["dropped"])
What it costs, in two places. A repair call counts double against the 2,000 calls a day a free key carries. A reground decision spends one extra model call, billed by your own provider. A model turn with no tool calls, and a tool the mapping does not name, cost nothing and pass through untouched.
What we measured
On DBOS's own Hacker News research agent, vendored unchanged and run on gpt-4o-mini through OpenRouter across five topics at ten iterations each, the agent as published repeated 14 of 50 searches and re-read 42 percent of the threads it fetched. With the repair in front of the one step where it proposes its next queries, repeats fell to 0 of 50, re-reads to 12 percent, and distinct threads covered rose 35 percent at the same model and the same iteration count. Across 45 follow-up steps the repair let 23 through untouched, filtered 21, and regrounded 1. Every run, the trace, and the command sit in the coherence census.
Those numbers come from an agent that hands out its proposed next queries explicitly. Your mileage depends on how much of your agent's state the tool mapping can see.
Links
- right-rudder, the read and its adapters for LangGraph, CrewAI, Letta, OpenInference, DBOS, and coding-agent edit logs
- Coherence census, every framework the read has run on, with a trace per row
- Research and the paper, SSRN 6683578
MIT licensed.
If the read caught something in your own run, a star on this repository helps other teams find it.
Metadata
Release files for langchain-right-rudder 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| langchain_right_rudder-0.4.0.tar.gz | 23.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| langchain_right_rudder-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 41.5 kB
Release files / langchain_right_rudder-0.4.0.tar.gz
| Download URL | langchain_right_rudder-0.4.0.tar.gz |
|---|---|
| Size | 23.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b1626a800d66d9248f706ff0deaf635acd6f49b75af0d0b2696250cf61828778
|
|
BLAKE2b-256 checksum How to use checksums |
1f34c53c66aa07ab8eca07ddb7c3a9a98c0aa80cb31eed548febad49a47480a7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 2, 2026.
Transparency logRelease files / langchain_right_rudder-0.4.0-py3-none-any.whl
| Download URL | langchain_right_rudder-0.4.0-py3-none-any.whl |
|---|---|
| Size | 18.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
17b4c48e0284d500f5cb90baa2f7297e450c06d5f3bbbb37077e928e4ec8da9d
|
|
BLAKE2b-256 checksum How to use checksums |
d4af9282d929f208778af2f84b37b51e448061b079c53251b1a6c0d0f8450e69
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 2, 2026.
Transparency log