Skip to main content

stryda-langchain

Drop Stryda governance into a LangChain agent with ~3 lines of wiring.

This package is the LangChain-native surface on top of stryda-sdk. It ships two adapters — a tool wrapper and a callback handler — so you can pick whichever matches your existing agent structure. Both paths funnel every tool call through Stryda's stryda.check_action → execute → stryda.record_outcome pipeline.

Install

Not published to PyPI yet. Install from this monorepo:

pip install -e ./packages/stryda-sdk-python
pip install -e ./packages/stryda-langchain

Requires Python 3.10+ and langchain-core >= 0.3.

Wrap any BaseTool. The LLM sees the same tool (same name, description, args_schema), but every invocation goes through Stryda first. On deny/escalate the agent sees a readable policy reason as the tool output and can course-correct.

from langchain.agents import create_openai_tools_agent, AgentExecutor
from langchain_core.tools import tool
from stryda_sdk import StrydaClient
from stryda_langchain import StrydaToolWrapper

stryda = StrydaClient(api_key=os.environ["STRYDA_API_KEY"])

@tool
def refund_charge(charge_id: str, amount_cents: int) -> dict:
    """Refund a Stripe charge."""
    return stripe.Refund.create(charge=charge_id, amount=amount_cents)

# One-liner — the rest of your agent setup is unchanged.
governed_refund = StrydaToolWrapper(
    tool=refund_charge,
    action_type="payments.refund",
    stryda_client=stryda,
)

executor = AgentExecutor(
    agent=agent,
    tools=[governed_refund, other_tools...],
)

What happens on each invocation:

Stryda decision Wrapped tool runs? Agent sees
allow Yes Real tool result; record_outcome=success
deny No "[stryda:denied] refund_charge was not run. Reason: …"
escalate No "[stryda:pending_approval] … (escalation_id=esc_xyz)"
Stryda 5xx No StrydaError raised — fail closed by design
Tool raises — record_outcome=error, exception re-raised

Option B — StrydaCallback

If you can't (or don't want to) rewrite your tool objects, attach the callback at the AgentExecutor level. It governs every tool call in the agent loop.

from stryda_langchain import StrydaCallback

executor = AgentExecutor(
    agent=agent,
    tools=tools,                        # unchanged — still BYO-key
    callbacks=[StrydaCallback(
        stryda_client=stryda,
        action_type_map={
            "refund_charge":  "payments.refund",
            "send_email":     "comms.email_send",
            "create_ticket":  "crm.ticket_create",
        },
    )],
)

The callback has raise_error = True, so a DeniedError raised from on_tool_start propagates through LangChain's callback manager and the tool is never executed. Unmapped tool names fall back to the tool's lowercased name coerced into Stryda's action_type regex.

Why only one class? Earlier versions of this package shipped a separate AsyncStrydaCallback subclassing AsyncCallbackHandler. It had a silent correctness bug — when an agent used ainvoke on a sync tool, LangChain's dispatcher sometimes routed async handlers through _run_coros, which swallows exceptions unconditionally. A denied decision would then log-and-allow. The single sync-methods class avoids that path entirely. The import AsyncStrydaCallback is still exported as an alias of StrydaCallback for back-compat.

Offline attestation verification

Every authorized tool call produces a signed Ed25519 JWT. To verify stored attestations without hitting Stryda, use stryda-sdk's verifier:

from stryda_sdk import fetch_jwks, verify_attestation

jwks = fetch_jwks("https://api.stryda.ai")   # cache per-process
claims = verify_attestation(jwt_token, jwks, audience="mcp-governance")

BYO-key stays BYO-key

Stryda never holds your Stripe / Gmail / Salesforce credentials. StrydaToolWrapper calls your original tool's _run with the original args; StrydaCallback lets LangChain execute your tool as it normally would. Stryda only authorizes and records. If your Stryda endpoint is unreachable, the wrapper fails closed (StrydaError) — it will never silently bypass governance.

Troubleshooting

Callback raised DeniedError but LangChain warned and ran the tool anyway. You probably attached a handler that subclasses AsyncCallbackHandler with async def methods on a sync tool. Use StrydaCallback — it works in both sync and async paths.

Tool ran but no attestation in Stryda's ledger. Check the check_id in your logs. record_outcome is best-effort (the action already happened); transient Stryda 5xx errors on record do NOT roll back the tool call. Your ledger-verify endpoint (GET /api/ledger/verify) will surface the gap if it drifted.

Wrapper is blocking LLM tool calling. Make sure you passed tool= (not tool_cls=) a concrete instance of BaseTool, not a class. LangChain introspects args_schema on the instance.

  • stryda-sdk-python — the underlying HTTP client + governed context manager
  • stryda-langgraph — policy node for LangGraph state machines
  • Mission + architecture: MISSION.md, docs/system-architecture.md

Metadata

Release files for stryda-langchain 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-langchain 0.1.0
File Size Uploaded
stryda_langchain-0.1.0.tar.gz 10.3 kB Details

Built distribution (wheel)

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

Total release size: 22.2 kB

Release files / stryda_langchain-0.1.0.tar.gz

Download URL stryda_langchain-0.1.0.tar.gz
Size 10.3 kB
Tags Source
SHA-256 checksum
How to use checksums
494b18b11d9695a04cdb19861e2112c785478af5246d9343b3b5e9ba628d1613
BLAKE2b-256 checksum
How to use checksums
f6e954bcdb51745d9b221430f12c4a36fd21fad3051d1e2f6431a693b3bba755
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.9

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

Download URL stryda_langchain-0.1.0-py3-none-any.whl
Size 12.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
17263da219d344c9adef56582aba7ab4ba9665b06ea1a77cfcf38b222b980503
BLAKE2b-256 checksum
How to use checksums
5314a03b2569bd8b4dbc1ef851f000ed2a2e385139efab2fc231719b90c5893e
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