Skip to main content

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 is deny; if any is escalated the aggregate is escalate.
  • 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) — a ToolMessage per 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 into policy_check once the escalation resolves. check_action is idempotent when called with the same idempotency_key, so re-checking replays the approved decision.
  • Split deny vs. escalate UX — route "deny" straight to END (unrecoverable) and only "escalate" back to agent (recoverable after approval).
  • stryda-sdk-python — underlying HTTP client
  • stryda-langchain — same story for plain LangChain agents (non-graph)
  • Mission + architecture: MISSION.md, docs/system-architecture.md

Metadata

Release files for stryda-langgraph 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for stryda-langgraph 0.1.0
File Size Uploaded
stryda_langgraph-0.1.0.tar.gz 6.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for stryda-langgraph 0.1.0
File Interpreter ABI Platform
stryda_langgraph-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 12.6 kB

Release files / stryda_langgraph-0.1.0.tar.gz

Download URL stryda_langgraph-0.1.0.tar.gz
Size 6.0 kB
Tags Source
SHA-256 checksum
How to use checksums
feb65c13f0525a7c82af70de4189d8ac5d34f381dbd40eb542b665c87e93883a
BLAKE2b-256 checksum
How to use checksums
54bc7a32ceb96712081f64fedbef8f6432e642c751e1fb475a30e9d43ac68a16
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.9

Release files / stryda_langgraph-0.1.0-py3-none-any.whl

Download URL stryda_langgraph-0.1.0-py3-none-any.whl
Size 6.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0d290d91e948516cd7234e7b9a8dac4e74517f9f83219f333e9d03f2ea264f3c
BLAKE2b-256 checksum
How to use checksums
abaabb83a3c2236145db379ce9fbb0e5a3d9fd0cee74347b44cee28c1fa9e21d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.9

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page