A minimal LangGraph guard node that wires the CodeRifts verdict directly into the agent loop.
Before the agent acts on a tool whose API contract may have drifted, the guard node diffs
the tool's old vs new spec against the zero-auth CodeRifts endpoint
(POST https://app.coderifts.com/api/v1/demo). On BLOCK the graph routes to abort and
the unsafe call never runs; on a safe verdict it proceeds to execute. No API key required.
pip install langgraph
python coderifts_langgraph_guard.py
On Python 3.9, pin the compatible release: pip install "langgraph==0.6.11".
[guard] get_order_status: execution_action='STOP' reason=STOP ... patterns=[FIELD_REMOVAL]
[abort] CodeRifts STOP -> get_order_status not called, agent halted
final: aborted get_order_status (CodeRifts STOP)
The sample before/after pair renames the response field order_status to status.
CodeRifts flags FIELD_REMOVAL (HIGH) and the TOOL_RESULT_SHAPE_DRIFT reflex escalates the
decision to BLOCK even though the raw risk score is low, so the agent halts before calling
the tool against the now-incompatible contract.
Swap OLD_SPEC / NEW_SPEC for your own before/after pair to watch the verdict shift.
The same @coderifts_guard decorator works on any framework's tools. Two
siblings live alongside the LangGraph node, both invoking the tool directly with
no LLM so the trace is deterministic:
coderifts_langchain_guard.pya LangChain tool.pip install langchain-corethenpython coderifts_langchain_guard.py.coderifts_autogen_guard.pyan AutoGen tool registered for execution.pip install "pyautogen<0.3"thenpython coderifts_autogen_guard.py.
On the breaking change the tool body never runs (the guard raises before it); on the safe additive change it does.
Control flow follows /.well-known/coderifts.json → recommended_usage:
branch on execution_action, not decision / omega_decision / safe_for_agent.
The LangGraph node and @coderifts_guard share evaluate_verdict in
coderifts_decorator.py so they cannot diverge.
execution_action |
Guard |
|---|---|
CONTINUE |
Proceed |
CONTINUE_WITH_MONITORING |
Proceed only if monitoring_sink_wired=True |
REQUEST_APPROVAL |
Halt (approval is not optional) |
STOP |
Halt |
| anything else, present | Halt as unrecognised — never fall back to decision |
| absent | Legacy decision→action map, then the same rules |
strict= is deprecated and has no remaining job: under the published floor
REQUEST_APPROVAL already always halts.
@coderifts_guard(old_spec, new_spec)
def call_tool(...): ...
# MONITOR action only proceeds when the host asserts a sink is wired:
@coderifts_guard(old_spec, new_spec, monitoring_sink_wired=True)
def call_tool(...): ...On a halt the guard raises CodeRiftsBlocked. Inspect err.execution_action,
err.reason, and err.verdict. err.decision is diagnostic only.
Offline checks (no network, stdlib only):
python3 test_execution_action.py
Everything above stops at the decision: ask the zero-auth endpoint whether a
contract change is safe, branch on execution_action, act. That is the whole of
coderifts_langgraph_guard.py, and it needs no key.
A decision is not a grant. A decision says "this looks safe". An execution grant says this exact change, by this executor, against this target, once — it binds the bytes, and an executor consumes it a single time. An executor can refuse to act without a grant. It has nothing to refuse on a decision.
coderifts_langgraph_atomic.py is that second path, as a second node. It asks
the keyed endpoint for a decision and a grant, and carries the grant to
whatever performs the mutation. The server mints it; this module requests and
carries it, and signs nothing itself.
from coderifts_langgraph_atomic import build_app # 1. atomic graph
app = build_app(); app.invoke({..., "target_uri": "https://api.example.com/orders"})export CODERIFTS_API_KEY=cr_live_...
python coderifts_langgraph_atomic.py
Without CODERIFTS_API_KEY it raises a named error and stops. It does not
fall back to the zero-auth endpoint: that endpoint cannot mint a grant, so a
fallback would hand you a verdict while your code believed it held a grant.
The decision-only path is untouched by any of this — a separate file, a separate
graph, and test_atomic_wire_fields.py asserts the two share nothing but the
evaluate_verdict helper.
Check the wire shape with no key, no network and no langgraph installed:
python3 -m unittest test_atomic_wire_fields
It asserts the request field set against the generated canonical fixture in
coderifts-app, and skips with a named reason when that sibling checkout is
absent rather than passing quietly.
This example ends where the grant is handed over. Two places show what happens after, and neither needs an API key:
- capability-demo
examples/atomic-v2—node examples/atomic-v2/run.js. No network, no key, no database, about a second. Four hops asserted end to end: the authorize request shape, grant issuance, one-use consumption, and seal verification — plus a control that a forged signature over identical bytes is refused. node bin/prove-all.jsin the same repository — boots a throwaway Postgres, runs the six panel proofs and the ten chain points in one process, and writestranscript.jsonandTRANSCRIPT.md. The tenth point re-verifies that transcript with the network trapped, so the artifact's own offline claim is demonstrated rather than asserted.