Skip to main content

bulwark-sdk (Python)

Governance, audit and policy decisions for AI agents.

Zero runtime dependencies — stdlib only. Python 3.9+.

Install

Not yet on PyPI. Install from git:

pip install bulwark-sdk

If that fails with "No matching distribution found", the package is not on PyPI yet. Two alternatives, both fully supported:

  • Use the REST API directly. Three calls, no install. See docs/API.md §2.
  • Vendor it. Copy src/bulwark/ into your project. Zero dependencies, nothing to install.

The one thing to understand first

There are two calls, and they do different jobs:

Call Endpoint What happens Counts as a decision?
observe() POST /v1/events Appended to the audit chain. No policy is evaluated. Buffered + batched. No
decide() POST /v1/decide Policy is evaluated. Returns allow/deny/escalate, can raise a human approval. Yes

If you only call observe(), your dashboard shows activity but zero decisions, zero flags, and a risk score driven only by registry facts. That is working as designed. Call decide() at the point your agent is about to do something that matters.

Bulwark never blocks your agent. decide() returns a verdict; acting on it is your code's job.

Quickstart

from bulwark import BulwarkClient

bw = BulwarkClient(api_key="bw_live_...")  # Settings → API keys

# 1. Register once (at deploy time, not per request)
agent = bw.register_agent(
    name="Portfolio Assistant",
    purpose="Answers investor questions about portfolio performance",
    risk_tier="medium",
    environment="prod",
    tools=["portfolio.read", "ai.assistant.answer"],
)
bw.activate_agent(agent["agentId"])   # agents are created "pending"

# 2. Record what the agent did
bw.observe(agent["agentId"], "ai.assistant.answer", resource="portfolio")

# 3. Ask for a verdict before something consequential
verdict = bw.decide(agent["agentId"], "crm.write", resource="customers")
if verdict.pending:
    log.warning("Awaiting human approval: %s", verdict.approval_id)
elif not verdict.allowed:
    log.warning("Bulwark would block this: %s", verdict.reason)

Three things that surprise people

1. Agents start pending, not active. decide() denies anything from a non-active agent with agent_not_active. Call activate_agent() after registering.

2. tools is currently descriptive, not enforced. Declaring tools=["crm.read"] records the agent's intended allowlist and drives the access-matrix view, but Bulwark does not yet reject or escalate an action outside it. Today this returns allow:

bw.register_agent(..., tools=["crm.read"])
bw.decide(agent_id, "crm.write")   # -> allow, "no matching policy — default allow"

To enforce scope today, author an explicit policy (POST /v1/policies) with a rule matching the actions you want escalated. Declare tools anyway — it is the allowlist scope enforcement will read once it ships.

3. Events for an unregistered agent are dead-lettered, not rejected. A typo'd agent_id fails quietly — the event lands in /v1/events/dead-letter rather than erroring. The SDK emits a warning when the API reports dead-lettered events.

Serverless

The background flusher does not run once your process is frozen or torn down. Flush before you return:

def handler(event, context):
    bw.observe(agent_id, "doc.process", resource=event["key"])
    ...
    bw.flush()      # or: with BulwarkClient(...) as bw:
    return {"ok": True}

flush() guarantees that everything buffered when you called it has been sent before it returns.

Failure behaviour

fail_open=True (the default) is the monitor-mode contract: governance being down must never take your agent down.

  • observe() never raises. Failed batches are dropped and counted in bw.dropped_events.
  • decide() returns an allow marked degraded=True. Never treat a degraded verdict as a real governance record — it means no policy was evaluated.

Set fail_open=False to surface errors as exceptions instead.

Don't send content

Never pass prompts, completions, or document bodies in context. Bulwark strips content fields at ingest by design, but the cheapest data to protect is the data you never transmit. Send metadata: identifiers, action names, resource names, counts.

Configuration

BulwarkClient(
    api_key,
    base_url="https://bulwark-api.fly.dev",
    timeout=5.0,
    batch_size=100,       # API hard cap is 100 events per batch
    flush_interval=2.0,   # background flush cadence, seconds
    fail_open=True,
    max_queue=10_000,     # buffer bound; oldest dropped beyond it
    max_retries=3,        # 429 and 5xx only, honours retry-after
)

API

Method Purpose
register_agent(name, purpose, risk_tier, environment, *, tools, data_sources, models_used, scope, category, owner_user_id, spend_limit_gbp) Register. Returns the agent dict (state pending).
activate_agent(agent_id) Transition to active.
set_agent_state(agent_id, state) draftpendingactivesuspended/retired.
list_agents() All agents for your tenant.
observe(agent_id, action, *, resource, on_behalf_of, context, type) Buffered telemetry.
decide(agent_id, action_type, *, resource, data_classification, context) Policy verdict.
flush(timeout=None) Send everything buffered.
close() Flush and stop the worker. Runs at exit automatically.

Exceptions

All inherit BulwarkError. BulwarkAuthError (401), BulwarkPermissionError (403), BulwarkUpgradeRequired (402, has .feature), BulwarkRateLimited (429, has .retry_after), BulwarkPayloadTooLarge (413), BulwarkValidationError (400, .body["details"]), BulwarkTransportError (network).

An API key maps to the developer role: it can register agents, send events and call decide(), but cannot mint keys, manage team members, or change the plan — those need an admin session.

Tests

pip install -e ".[dev]"
python -m pytest

Download files

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

Source Distribution

bulwark_sdk-0.1.0.tar.gz (17.0 kB view details)

Uploaded Source

Built Distribution

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

bulwark_sdk-0.1.0-py3-none-any.whl (13.4 kB view details)

Uploaded Python 3

File details

Details for the file bulwark_sdk-0.1.0.tar.gz.

File metadata

  • Download URL: bulwark_sdk-0.1.0.tar.gz
  • Upload date:
  • Size: 17.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.3

File hashes

Hashes for bulwark_sdk-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e3e1aa9b28b985c5489a69c1c5067edaa9db868550a526bc11470ed2e887203c
MD5 490911193dc5b6e49ca1a0bbb3ec0e81
BLAKE2b-256 14632bbb3616987f523aaf44b6718185082e0b11be36569d929f511f7d935fe3

See more details on using hashes here.

File details

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

File metadata

  • Download URL: bulwark_sdk-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 13.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.3

File hashes

Hashes for bulwark_sdk-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 87d6216b68b19fbc59a871dad2e7551c255996b49d2d89dea67cd52927bd64cd
MD5 861ec6743ae8041894e93206085a66a6
BLAKE2b-256 5d7343340b5220a941584c49c06d0b112a7c2629adda8c68d950f6d3415ea2df

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 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