Skip to main content

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

Readsstate["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).

Related

  • stryda-sdk-python — underlying HTTP client
  • stryda-langchain — same story for plain LangChain agents (non-graph)
  • Mission + architecture: MISSION.md, docs/system-architecture.md

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

stryda_langgraph-0.1.0.tar.gz (6.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

stryda_langgraph-0.1.0-py3-none-any.whl (6.6 kB view details)

Uploaded Python 3

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

Hashes for stryda_langgraph-0.1.0.tar.gz
Algorithm Hash digest
SHA256 feb65c13f0525a7c82af70de4189d8ac5d34f381dbd40eb542b665c87e93883a
MD5 fc2fe3f1d14b6e554b22c2f90aece983
BLAKE2b-256 54bc7a32ceb96712081f64fedbef8f6432e642c751e1fb475a30e9d43ac68a16

See more details on using hashes here.

File details

Details for the file stryda_langgraph-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for stryda_langgraph-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0d290d91e948516cd7234e7b9a8dac4e74517f9f83219f333e9d03f2ea264f3c
MD5 ed8bfe15395984bf317f8a0f3ddccfc0
BLAKE2b-256 abaabb83a3c2236145db379ce9fbb0e5a3d9fd0cee74347b44cee28c1fa9e21d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page