BobSentry logoBobSentry

Protect a production deployment with BobSentry

Canonical ~10-minute tutorial. An agent intends deploy_production, BobSentry returns ALLOW / REQUIRE_APPROVAL / BLOCK, and your integration honors that decision. A fake local deploy runs only when authorization conditions are satisfied. BobSentry never performs the deployment.

Trust boundary

BobSentry returns the authorization decision. Your integration is responsible for honoring that decision and performing or withholding the downstream action.

Why deploy_production?

Production deploy is a clear consequential execution boundary: if the integrator runs it without authorization, systems change. The same model applies to other high-impact actions (this page does not tutorialize them): PR merge, infrastructure mutation, database/schema changes, IAM changes, and consequential tool calls.

Prerequisites

  1. A BobSentry organization / account
  2. Create an Agent (see Software Agents)
  3. Create / reveal an Agent-bound API credential
  4. Note the Agent ID (must match the credential's boundAgentId when present)
  5. Choose the Python or TypeScript SDK below

Install

Public packages: bobsentry 0.4.0 on PyPI (Python ≥3.12) and @bobsentry/sdk 0.1.0 on npm (Node ≥20, ESM).

Python

pip install bobsentry
# Optional pin:
# pip install bobsentry==0.4.0

TypeScript

npm install @bobsentry/sdk
# or
pnpm add @bobsentry/sdk

Environment

export BOBSENTRY_API_KEY="bsk_..."
export BOBSENTRY_AGENT_ID="infra-agent"

Decision model

  • ALLOW — integrator may proceed.
  • REQUIRE_APPROVAL — do not execute yet. A human must authorize in the BobSentry console.
  • BLOCK — do not execute. No override, no auto-retry.

Authorization ≠ execution ≠ outcome. Customer agent modes are COOPERATIVE and OBSERVE. Unknown / unmatched consequential actions retain a safe REQUIRE_APPROVAL fallback.

Approval semantics (critical)

REQUIRE_APPROVAL is the original authorization decision. Human approval satisfies that requirement; the decision record remains REQUIRE_APPROVAL and approval_status becomes APPROVED. That combination allows the integrator to proceed. Do not rewrite REQUIRE_APPROVAL to ALLOW in your code or docs.

Statedecisionapproval_requiredapproval_statusMay proceed?
ALLOWALLOWfalseNOT_REQUIREDYes
Pending approvalREQUIRE_APPROVALtruePENDINGNo
ApprovedREQUIRE_APPROVALtrueAPPROVEDYes
DeniedREQUIRE_APPROVALtrueDENIEDNo
ExpiredREQUIRE_APPROVALtrueEXPIREDNo
BlockedBLOCK——No

Proceed when: decision == ALLOW or (decision == REQUIRE_APPROVAL and approval_status == APPROVED).

After an approved REQUIRE_APPROVAL, SDK helpers stay requires_approval / requiresApproval = true and is_allowed / isAllowed = false. Do not use is_allowed / isAllowed as the post-approval proceed condition.

Python

Uses BobSentry.authorize and get_decision. Repo example: bobsentry-runtime/sdk/python/examples/authorize_deploy_production.py

import os
from bobsentry import BobSentry

bob = BobSentry(
    api_key=os.environ["BOBSENTRY_API_KEY"],
    agent_id=os.environ["BOBSENTRY_AGENT_ID"],
)

def fake_deploy() -> None:
    print("FAKE DEPLOYMENT: no production system was changed")

decision = bob.authorize(
    action="deploy_production",
    action_environment="production",
    context={"service": "payments"},
)

print(f"decision={decision.decision} id={decision.decision_id}")

if decision.is_allowed:
    fake_deploy()

elif decision.requires_approval:
    print("REQUIRE_APPROVAL: do not deploy yet.")
    print(f"Approve in the BobSentry console, then check: {decision.decision_id}")
    # Preferred 10-minute path: approve in the UI, then call get_decision once.
    # Do not treat is_allowed as true after approval — it stays false.
    latest = bob.get_decision(decision.decision_id)
    if (
        latest.decision == "REQUIRE_APPROVAL"
        and latest.approval_status == "APPROVED"
    ):
        fake_deploy()
    else:
        print(f"Still waiting or denied: approval_status={latest.approval_status}")

elif decision.is_blocked:
    print("BLOCK: do not deploy.")
    if decision.reasonMessage:
        print(f"reason: {decision.reasonMessage}")

TypeScript

Uses bob.authorize and bob.getDecision. Requires Node ≥20 and ESM (import / "type": "module"; CommonJS is not supported). Repo example: bobsentry-runtime/sdk/typescript/examples/authorize-deploy-production.ts

import { BobSentry } from "@bobsentry/sdk";

const bob = new BobSentry({
  apiKey: process.env.BOBSENTRY_API_KEY!,
  agentId: process.env.BOBSENTRY_AGENT_ID!,
});

function fakeDeploy(): void {
  console.log("FAKE DEPLOYMENT: no production system was changed");
}

const decision = await bob.authorize({
  action: "deploy_production",
  actionEnvironment: "production",
  context: { service: "payments" },
});

console.log(`decision=${decision.decision} id=${decision.decisionId}`);

if (decision.isAllowed) {
  fakeDeploy();
} else if (decision.requiresApproval) {
  console.log("REQUIRE_APPROVAL: do not deploy yet.");
  console.log(`Approve in the BobSentry console, then check: ${decision.decisionId}`);
  // Preferred 10-minute path: approve in the UI, then call getDecision once.
  // Do not wait for isAllowed to become true — it stays false after approval.
  const latest = await bob.getDecision(decision.decisionId);
  if (
    latest.decision === "REQUIRE_APPROVAL" &&
    latest.approvalStatus === "APPROVED"
  ) {
    fakeDeploy();
  } else {
    console.log(`Still waiting or denied: approvalStatus=${latest.approvalStatus}`);
  }
} else if (decision.isBlocked) {
  console.log("BLOCK: do not deploy.");
  if (decision.reasonMessage) {
    console.log(`reason: ${decision.reasonMessage}`);
  }
}

Preferred 10-minute path

  1. Run authorize for deploy_production.
  2. If REQUIRE_APPROVAL, open Approvals and approve the ticket.
  3. Call get_decision / getDecision once and proceed only when REQUIRE_APPROVAL + APPROVED (or original ALLOW).

Long-running wait loops are optional advanced usage (see the Python SDK wait_for_approval helpers). They are not required for this tutorial.

Next steps

Protect a Production Deployment | BobSentry Docs | BobSentry