Python SDK Quickstart
Public package bobsentry 0.4.0. Install with pip install bobsentry (Python ≥3.12). The SDK calls POST /v1/runtime/evaluate with x-api-key, then honors ALLOW / REQUIRE_APPROVAL / BLOCK in your process. BobSentry returns the authorization decision. Your integration is responsible for honoring that decision and performing or withholding the downstream action. Canonical HTTP contract: Runtime Integration Contract. Start with the production deploy quickstart for the end-to-end authorize → approve → proceed path.
Install
pip install bobsentry # Optional pin: # pip install bobsentry==0.4.0
Requires Python 3.12+.
Authorize (public client)
Preferred public API: BobSentry.authorize and get_decision. Does not wait for approval and does not run the downstream action.
from bobsentry import BobSentry
bob = BobSentry(
api_key="bobsentry_live_xxx",
agent_id="infra-agent",
base_url="https://runtime.bobsentry.com",
)
decision = bob.authorize(
action="deploy_production",
action_environment="production",
context={"service": "payments"},
)
# Proceed only on ALLOW, or REQUIRE_APPROVAL + approval_status APPROVED.
# Approved REQUIRE_APPROVAL does not become ALLOW.
if decision.is_allowed:
...
elif decision.requires_approval:
latest = bob.get_decision(decision.decision_id)
if latest.decision == "REQUIRE_APPROVAL" and latest.approval_status == "APPROVED":
...
elif decision.is_blocked:
...Protect a function (Sentry guard)
Compatibility path: decorator-based protection with the same runtime evaluate contract.
from bobsentry import Sentry, SdkConfig
sentry = Sentry(
api_key="bobsentry_live_xxx",
agent_id="ehr-agent",
base_url="https://runtime.bobsentry.com",
config=SdkConfig(fail_mode="closed"), # closed | open | observe
)
@sentry.guard(action="send_external_email", target="external_email")
def send_email(to: str, body: str) -> None:
# BobSentry evaluates BEFORE this body runs. If the decision is not ALLOW,
# this line never executes.
smtp_send(to, body)What happens on each call
- - The SDK classifies args locally and sends metadata only to
/v1/runtime/evaluate(authenticated withx-api-key). Raw payloads never leave your environment. - - The runtime returns a signed decision:
ALLOW,REQUIRE_APPROVAL, orBLOCK. Authorization ≠ execution ≠ outcome. - - On
ALLOW, the SDK runs your function. OnREQUIRE_APPROVAL/BLOCK, it raises (unless you opt into approval polling). The SDK — not BobSentry — performs any local execution.
Async is supported
@sentry.guard(action="deploy_production", target="prod-cluster")
async def deploy(service: str, version: str) -> str:
return await do_deploy(service, version)Protect an existing tool
Framework-agnostic, no arg serialization, preserves callable metadata.
from bobsentry import Sentry from bobsentry.integrations import protect_tool sentry = Sentry(api_key="...", agent_id="ops-agent") # Wrap any existing sync or async callable (a plain function, a LangChain # StructuredTool.func, an OpenAI tool dispatcher, ...). protect_tool never # serializes the tool's args/kwargs into the request. safe_deploy = protect_tool(sentry, deploy, action="deploy_production")
Wait for approval (optional)
Opt in per guard. The SDK polls only the existing decision id and never re-runs authorization. A human approves in the console; a tenant key cannot self-approve.
@sentry.guard(
action="run_database_migration",
target="prod-db",
wait_for_approval=True,
approval_timeout_s=600,
approval_poll_interval_s=5,
)
def run_migration(migration_id: str) -> str:
return apply(migration_id)
# APPROVED -> runs exactly once
# DENIED -> ExecutionDeniedError
# EXPIRED -> ApprovalExpiredError
# timeout -> ApprovalTimeoutError (ticket may still be PENDING server-side)
# NOT_REQUIRED / unknown / malformed -> ApprovalProtocolError (never executes)Local simulation (no key, no network)
# Offline: no API key, no network. Explore decisions before wiring the runtime.
from bobsentry import Sentry
sentry = Sentry(agent_id="dev-agent", simulate=True) # canonical activation
sentry.simulation_engine.add_policy("external_api_call", "REQUIRE_APPROVAL")
@sentry.guard(action="delete_repository")
def wipe(name: str) -> None:
... # simulated decision is BLOCK -> ExecutionBlockedError
# Simulated decisions are NON-PRODUCTION: source=LOCAL_SIMULATION,
# signed_evidence=False, policy_id begins with "LOCAL-SIM-".Verify signed evidence
bobsentry verify ./evidence-bundle.json
# {"verified": true, "checks": [...]} (local-simulation records are rejected)See the Runtime Integration Contract, developer overview, or the audit evidence guide for more.