stryda-sdk (Python)
One-file wrapper that puts a Stryda governance check in front of any tool call your agent makes, records the outcome afterwards, and hands you back a verifiable attestation.
This is Stryda's Layer 3 enforcement path — for agents that cannot route through MCP (custom scripts, third-party frameworks you do not control, internal services). For MCP-native agents, use /api/mcp directly — the pipeline at backend/mcp/pipeline.py already governs every call.
See docs/system-architecture.md for the full picture.
Install
Not published to PyPI. Install from the repo:
pip install -e ./packages/stryda-sdk-python
# Optional — offline attestation verification
pip install -e "./packages/stryda-sdk-python[verify]"
Quick start
import os
import stripe
from stryda_sdk import StrydaClient, governed, PolicyDenied, PendingApproval
stryda = StrydaClient(
api_key=os.environ["STRYDA_API_KEY"],
base_url=os.environ.get("STRYDA_BASE_URL", "https://api.stryda.ai"),
)
stripe.api_key = os.environ["STRIPE_KEY"]
def refund(charge_id: str, cents: int):
try:
return governed(
stryda,
tool="stripe.create_refund",
action_type="payment_refund",
args={"charge_id": charge_id, "amount_cents": cents},
cost_estimate=cents / 100,
agent_id="billing-agent",
idempotency_key=f"refund:{charge_id}:{cents}",
execute=lambda: stripe.Refund.create(charge=charge_id, amount=cents),
)
except PolicyDenied as e:
print(f"Stryda denied refund: {e.reason}")
except PendingApproval as e:
print(f"Refund needs approval: {e.escalation_id}")
Manual two-step
check = stryda.check_action(
tool="internal.delete_customer",
action_type="hard_delete",
args={"customer_id": customer_id},
)
if check.decision != "authorized":
... # handle denied / escalated
import time
started = time.monotonic()
try:
my_api.delete(customer_id)
stryda.record_outcome(
tool="internal.delete_customer",
check_id=check.check_id,
outcome="success",
latency_ms=int((time.monotonic() - started) * 1000),
)
except Exception as e:
stryda.record_outcome(
tool="internal.delete_customer",
check_id=check.check_id,
outcome="error",
latency_ms=int((time.monotonic() - started) * 1000),
error=str(e),
)
raise
Verifying attestations offline
Every record_outcome response includes an EdDSA JWT. The public key lives at https://api.stryda.ai/api/.well-known/jwks.json. The SDK's verifier:
from stryda_sdk import fetch_jwks, verify_attestation
jwks = fetch_jwks("https://api.stryda.ai") # cache this
claims = verify_attestation(record.attestation, jwks, audience="mcp-governance")
# claims["audit_id"], claims["tool"], claims["decision"], claims["sub"] (user_id), …
No Stryda-specific format — any JWT library that understands EdDSA with Ed25519 will verify these tokens against the JWKS. PyJWT[crypto] works out of the box:
import jwt, httpx
jwks = httpx.get("https://api.stryda.ai/api/.well-known/jwks.json").json()
claims = jwt.decode(
record.attestation,
key=jwt.algorithms.OKPAlgorithm.from_jwk(
next(k for k in jwks["keys"] if k["kid"] == record.attestation_kid)
),
algorithms=["EdDSA"],
audience="mcp-governance",
)
Error model
| Exception | Meaning |
|---|---|
StrydaError |
Transport or HTTP error from the control plane. |
PolicyDenied |
check_action returned denied. Do not execute the tool. |
PendingApproval |
check_action returned escalated. Surface escalation_id to the user / agent loop. |
governed() raises the first two for you. If execute() raises, record_outcome is still called with outcome="error" before the exception propagates. If both the tool and record_outcome fail, the tool exception wins — a best-effort audit write should not shadow the real error.
What this SDK does not do
- It does not proxy your API calls. Your process still talks to OpenAI / Stripe / Twilio directly.
- It does not cache decisions. A
deniedpolicy at T0 might beauthorizedat T1 after policy edits. - It does not retry. Use the same
idempotency_keyon retries — the control plane will return the stored decision instead of re-escalating.
Runtime requirements
- Python 3.10+.
httpx>=0.26.cryptography>=42only if you useverify_attestation(extra:stryda-sdk[verify]).
Metadata
Release files for stryda-sdk 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| stryda_sdk-0.1.0.tar.gz | 10.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| stryda_sdk-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 21.5 kB
Release files / stryda_sdk-0.1.0.tar.gz
| Download URL | stryda_sdk-0.1.0.tar.gz |
|---|---|
| Size | 10.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9cfde1becb1f028a21244b2b3a6182e5e158511db22e94397fbc694820826f1f
|
|
BLAKE2b-256 checksum How to use checksums |
30d092d1d2e95484b17739e170d0d318f97b0b91eaf54d23ee871fb45d4f8fd0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|
Release files / stryda_sdk-0.1.0-py3-none-any.whl
| Download URL | stryda_sdk-0.1.0-py3-none-any.whl |
|---|---|
| Size | 11.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b2f2c59fa98491d0ef3f7603420be0cfb19e1abc758d40206ecf4b474f7789a2
|
|
BLAKE2b-256 checksum How to use checksums |
b2b072cc84b2d25c9adc22819e59ba53b0b59f12c9692912c97389818b68ae14
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|