Skip to content

BuildPython SDK

Create ActionReceipts from Python.

Place Bulla in application code that authorizes or sends an action. Models do not need to know about the receipt.

Treat payments, permission changes, durable writes, and provider handoffs as consequential actions. Put wrap_action in the API gateway, tool router, payment handler, or agent runtime that performs one. Bulla writes an ActionReceipt for the counterparty to retain.

Supply the action and its declared context from application code; Bulla creates the JSON. Run bulla demo first to inspect the action log, receipt, changed copy, missing-receipt comparison, and local checker.

Wrap an action

Use the context manager to set a result, add evidence, and retrieve the emitted receipt:

python
from bulla import wrap_action, verify_receipt

with wrap_action("payments.charge", {"amount": 200, "currency": "USD"}) as act:
    act.set_result("sha256:...")                     # optional
    act.add_evidence("counterparty_ack", "sha256:...", "counterparty_signed")

receipt = act.receipt            # a dict
verify_receipt(receipt).ok       # True

Bulla emits a receipt even when the wrapped body raises: the outcome is markederror and the exception is re-raised, never swallowed. For a completed action with no body to wrap, receipt_for(action_type, subject) returns the receipt directly.

Decorate a tool

Decorate a tool function with the same object to emit one receipt per call:

python
from bulla import wrap_action

scope = wrap_action("fs.write", {"path": "/tmp/out.txt"})

@scope
def write_file(path, content):
    ...

write_file("/tmp/out.txt", "ok")
receipt = scope.last_receipt

State who authorized the action

Pass a principal and policy when another party needs to authenticate who authorized the action and which policy applied. Add an operational challenge endpoint, rollback window, or named remedy only when the transaction requires it:

python
from bulla import wrap_action, operational_envelope

env = operational_envelope(
    principal="did:web:acme#agent",
    policy="policy://payments@sha256:aa",
    forum_endpoint="https://log.example",
    forum_root="ots:root",
)
with wrap_action("payments.charge", {"amount": 200}, envelope=env) as act:
    ...

Pass a signer from the bulla[identity] extra when the counterparty needs to authenticate the receipt. An unsigned receipt can still be checked for internal consistency, but it does not authenticate its issuer.

Find the actions that left no receipt

A receipt checker can inspect only files that exist. To find actions with no receipt, compare the receipt set with a separate action log from your gateway, receiver, or test harness. event_coverage returns the unmatched actions:

python
from bulla import event_coverage

report = event_coverage(observed_actions, emitted_receipts, anchor="gateway-log")
report["unreceipted_delta"]     # actions with no covering receipt

Coverage reaches only actions present in the supplied log. Find a runnable covered example in examples/wrap-your-agent; the coverage walkthrough adds an action with no receipt.