Stryda governance as a LangGraph node — plug it between the agent LLM and the tool executor.
Project description
stryda-langgraph
Governance as a LangGraph node. Plug it between your agent LLM and your tool executor, and every tool call in the graph is gated on a Stryda stryda.check_action decision before it runs.
Install
Not on PyPI yet. Install from this monorepo:
pip install -e ./packages/stryda-sdk-python
pip install -e ./packages/stryda-langgraph
Requires Python 3.10+, langchain-core >= 0.3, langgraph >= 0.2.
Quickstart
from langgraph.graph import StateGraph, END, MessagesState
from langgraph.prebuilt import ToolNode
from stryda_sdk import StrydaClient
from stryda_langgraph import stryda_policy_node
stryda = StrydaClient(api_key=os.environ["STRYDA_API_KEY"])
graph = StateGraph(MessagesState)
graph.add_node("agent", agent_llm_node) # your existing LLM node
graph.add_node(
"policy_check",
stryda_policy_node(
stryda_client=stryda,
action_type_map={
"refund": "payments.refund",
"send_email": "comms.email_send",
},
),
)
graph.add_node("tool_executor", ToolNode(tools))
graph.set_entry_point("agent")
graph.add_edge("agent", "policy_check")
graph.add_conditional_edges(
"policy_check",
lambda s: s["stryda_decision"],
{
"allow": "tool_executor",
"deny": "agent", # LLM sees the deny reason and re-plans
"escalate": END, # halt until escalation resolves
},
)
graph.add_edge("tool_executor", "agent")
app = graph.compile()
What the node reads + writes
Reads — state["messages"]. The last entry must be an AIMessage with tool_calls populated (the standard MessagesState pattern).
Writes (state delta) — three keys:
stryda_decision:"allow" | "deny" | "escalate"— strictest wins. If any call is denied the aggregate isdeny; if any is escalated the aggregate isescalate.stryda_attestations:list[dict]— one entry per tool call:{ "tool_call_id": "call_xyz", "tool": "refund", "decision": "allow", # "allow" | "deny" | "escalate" "reason": "matched scope refund.tier2", "check_id": "chk_abc", # ← use to verify the Ed25519 attestation "escalation_id": None, "approval_poll_url": None, }
messages(only on deny/escalate) — aToolMessageper blocked call, carrying the policy reason. Without this, a denied call leaves an open-loop tool call and most prompt templates loop. Feeding the reason back lets the LLM course-correct.
Parallel fanout
If the LLM emits multiple tool calls in one step, the node fires check_action for each in parallel (asyncio.gather). Decisions are aggregated strictest-first — one deny halts the whole batch. No partial execution.
Fail-closed on Stryda outage
If Stryda itself is unreachable, check_action raises StrydaError and the graph halts. The node never silently allows a tool call through on a transport failure. (This is by design — see MISSION.md "do-not-do" list.)
Conditional edges — pattern
The standard wiring:
graph.add_conditional_edges(
"policy_check",
lambda s: s["stryda_decision"],
{"allow": "tool_executor", "deny": "agent", "escalate": END},
)
Variants:
- Retry after escalation approval — keep a "waiting" node with a sleep / human-approval webhook instead of
END, and route back intopolicy_checkonce the escalation resolves.check_actionis idempotent when called with the sameidempotency_key, so re-checking replays the approved decision. - Split deny vs. escalate UX — route
"deny"straight to END (unrecoverable) and only"escalate"back toagent(recoverable after approval).
Related
stryda-sdk-python— underlying HTTP clientstryda-langchain— same story for plain LangChain agents (non-graph)- Mission + architecture:
MISSION.md,docs/system-architecture.md
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file stryda_langgraph-0.1.0.tar.gz.
File metadata
- Download URL: stryda_langgraph-0.1.0.tar.gz
- Upload date:
- Size: 6.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
feb65c13f0525a7c82af70de4189d8ac5d34f381dbd40eb542b665c87e93883a
|
|
| MD5 |
fc2fe3f1d14b6e554b22c2f90aece983
|
|
| BLAKE2b-256 |
54bc7a32ceb96712081f64fedbef8f6432e642c751e1fb475a30e9d43ac68a16
|
File details
Details for the file stryda_langgraph-0.1.0-py3-none-any.whl.
File metadata
- Download URL: stryda_langgraph-0.1.0-py3-none-any.whl
- Upload date:
- Size: 6.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d290d91e948516cd7234e7b9a8dac4e74517f9f83219f333e9d03f2ea264f3c
|
|
| MD5 |
ed8bfe15395984bf317f8a0f3ddccfc0
|
|
| BLAKE2b-256 |
abaabb83a3c2236145db379ce9fbb0e5a3d9fd0cee74347b44cee28c1fa9e21d
|