Bulla
Receipts for Agents.
Bulla creates portable ActionReceipts for consequential agent transactions. A
receiving system can verify the record locally, apply its own ReliancePolicy,
and reconcile the receipt set against its own event records.
Answerable-computing profiles for inference procurement, witnessed history, consequence rules, and correction networks live in repository source. They are not part of the installed package.
Glyph Standard publishes the ActionReceipt format, Bulla, and the public test suite.
The application creates the receipt where an action is accepted or completed: an API gateway, tool router, payment handler, or agent runtime. The model does not need to know about Bulla or write JSON.
Install and run one transaction
Bulla supports Python 3.10 and later. Receipt creation and file-integrity checks run locally and require no hosted Bulla service.
python -m pip install "bulla==0.48.0"
bulla demo
The fixed demo creates a receipt for one constructed USD 125 payment, checks the saved file, rejects an altered copy, and compares the receipt set with a separately supplied receiver record containing one additional action.
FIRST ACTION DEMO · CONSTRUCTED LOCAL SCENARIO
ACTION
recorded action payments.charge
amount USD 125.00
declared limit USD 200.00
ALTERATION CONTROL
record integrity FAILED
original receipt UNCHANGED
OMISSION CONTROL
coverage before 1/1
coverage after 1/2
unreceipted action pay_demo_043
original integrity VERIFIED
The altered file fails its integrity check. The separate receiver record exposes an action with no matching receipt. Neither result establishes that funds moved or that the receiver record contains every action.
Use bulla demo --out DIR to choose a fresh output directory or bulla demo --format json for the versioned machine report. Bulla refuses to replace an
existing path.
Provider logs and receiver records
Provider logs are useful, and Bulla does not replace them. They usually describe an activity stream inside the provider's system, use a provider-specific schema, and remain under the provider's custody.
An ActionReceipt has a different job: hand the receiving party one portable record for one transaction. If a provider exports the relevant event, binds it to the buyer's request and accepted terms, authenticates it, and lets the buyer retain it, that export can become evidence for an ActionReceipt. The standard format means a buyer does not need a different log integration for every provider.
| Provider log | ActionReceipt | |
|---|---|---|
| Primary use | Operate and debug the provider | Hand one transaction to the receiving party |
| Custody | Usually controlled by the provider | Retained by each receiving party |
| Format | Provider-specific | Open and versioned |
| Scope | System activity stream | One action or transaction |
| Verification | Whatever the provider exposes | Local checks defined by the format |
| Completeness | Not assumed | Not assumed |
Buyers, gateways, and marketplaces can require receipt support before routing work or accepting a delivery. A provider that supports the format can qualify for those workflows and use the same agreed transaction file for acceptance, audit, and disputes. This repository does not claim that receipts improve payment speed, insurance pricing, or reputation.
The receipt is not the decision
An ActionReceipt preserves the request, accepted permissions and limits, reported result, and supplied evidence. It does not make the next decision. The customer, auditor, marketplace, or downstream agent applies its own policy to the retained record.
An eligible consequence is still separate from authority to execute it. A receipt does not establish that the reported event occurred or that the receiver record contains every relevant event.
Add Bulla where the application acts
wrap_action creates the JSON file around the application call:
from bulla import wrap_action
with wrap_action(
"payments.charge",
{"event_id": "pay-1", "amount_minor": 12500},
) as action:
action.set_result("sha256:" + "0" * 64)
receipt = action.receipt
The receipt can record the action claim, declared authority and limits, supplied evidence references, and challenge path. The exact fields are defined by the ActionReceipt standard.
Verify one receipt
Download the constructed payment receipt and check it locally:
curl -fsSLo constructed-payment-authorization-v0.2.json \
https://glyphstandard.com/examples/payment-authorization-v0.2.json
bulla receipt verify constructed-payment-authorization-v0.2.json --format json
The receipt records a USD 125.00 charge, declares a USD 200.00 limit, and carries an executable rule for checking the limit. The dimensional report includes:
integrity VERIFIED
authenticity UNVERIFIED
authority UNAUTHENTICATED
declared_bounds CONFORMS
grounding SELF_ASSERTED
recourse NAMED
reachability UNVERIFIED
reliance_decision NOT_COMPUTED
These are separate results, not one global safety or truth verdict. The same
receipt is available at spec/vectors/payment-authorization.json; its expected
result is pinned in spec/vectors/expected.json and recomputed in CI.
Check receipt coverage
event_coverage compares valid receipts with a receiver record supplied outside
the receipt set. For an exact saved-record match, add record_sha256 using
observed_record_sha256; the receipt must carry the same digest in its result or
evidence references. Without that field, coverage is action-ID correlation.
from bulla.action_receipt import verify_receipt
from bulla.coverage import event_coverage
from bulla.wrap import receipt_for
receipt = receipt_for("network.egress", {"event_id": "action-001"})
assert verify_receipt(receipt).ok
complete = event_coverage([{"id": "action-001"}], [receipt])
assert complete["coverage"] == 1.0
assert complete["unreceipted_delta"] == []
with_gap = event_coverage(
[{"id": "action-001"}, {"id": "action-002"}],
[receipt],
)
assert with_gap["coverage"] == 0.5
assert with_gap["unreceipted_delta"] == ["action-002"]
Receipt integrity is unchanged in the second comparison. The supplied receiver record contains one action with no matching receipt. Bulla does not establish that the record itself is complete.
Apply a receiver policy
ReliancePolicy turns a complete verification view into RELY, REFUSE, or
ESCALATE. Bulla publishes strict and pragmatic policies, plus an
evidence-strict policy that requires both a third-party-anchored or
execution-verified label and the receiver's acceptance of the exact evidence
digest/class pair through separately supplied grounding context.
from bulla import (
EVIDENCE_STRICT_RELIANCE_POLICY,
decide,
receipt_for,
verify_receipt,
)
receipt = receipt_for("network.egress", {"event_id": "action-001"})
verification = verify_receipt(receipt)
decision = decide(verification, EVIDENCE_STRICT_RELIANCE_POLICY)
assert decision.outcome == "refuse"
assert {item["dimension"] for item in decision.unmet} >= {
"verified_to", "effective_grounding"
}
The application chooses the policy and decides what downstream action follows.
Receipt-carried grounding labels do not satisfy the evidence-strict policy by
themselves. After validating an anchor or recomputing evidence, a receiver may
pass its resulting verified_evidence_grounding mapping to verify_receipt.
That external context is a receiver input, not a fact the packet may supply.
When the receiver records the result as a bulla.rely receipt, Bulla binds the
exact context hash into the signed subject; replay verification requires the
same context as a separate input. A serialized verification view cannot promote
its own grounding status.
Even then, a grounding class does not establish occurrence, worldly truth,
organizational independence, custody, settlement, or downstream effect.
Keep the verification kit
The package carries the v0.2 specification, constructed examples, expected dimensional reports, and a zero-dependency checker as one immutable archive:
bulla receipt kit --out action-receipt-v0.2-verification-kit.zip
Expected archive digest:
sha256:8f2cdd16bcbd1a1121f49545b6a6512872b188221ca30ec054dfd6b2fb2142ab
After extracting the archive, run python3 verify.py to check the kit or run
its standalone checker against a saved v0.2 receipt:
python3 verify.py receipt RECEIPT.json --format text
The checker imports no Bulla code and makes no network request. The manifest checks the archive contents. Authenticate the archive itself with the detached digest or signed release receipt.
bulla receipt drill runs both the installed Bulla checker and the retained
standalone checker while network access is denied:
bulla receipt drill RECEIPT.json --format text
Published package and source profiles
Bulla 0.48.0 ships ActionReceipt creation and verification, explicit reliance policy, and coverage reconciliation. The following examples are experimental repository-source profiles. They do not add installed commands or stable Python exports.
Experimental: evaluate one receiving policy
The source-only Acceptance Contract
alpha demonstrates an agent-to-agent
deployment handoff. A deploy agent reports that staging is ready; the receiving
release policy also requires a rollback-test record for the exact build and
contract. Missing evidence produces a conditional request, PASS permits
eligibility, and FAIL refuses it. Even the eligible result leaves
authorization unissued and execution unattempted.
PYTHONPATH=src python3 examples/acceptance-contract/run_demo.py \
--story --out /tmp/bulla-acceptance
python3 -I spec/acceptance-contract/check.py \
/tmp/bulla-acceptance/missing \
--context /tmp/bulla-acceptance/context.json \
--format story
This profile is inspectable in source and excluded from the installed package.
Experimental: trace a correction through declared reliance
The source-only Reliance Map profile starts from an accepted declared graph. When an authenticated correction notice targets one source digest, the verifier identifies the exact descendants that require rechecking. Complete branches with no declared path are reported separately; incomplete lineage remains unresolved.
The constructed corpus contains 10,000 declared decisions: 2,500 require rechecking, 5,000 have no declared path under the accepted graph, and 2,500 remain unresolved because their lineage is incomplete. Python and standalone Node produce the same report.
PYTHONPATH=src python3 spec/reliance-map/check.py \
spec/reliance-map/generated/graph.json \
--ledger spec/reliance-map/generated/correction-ledger.json \
--context spec/reliance-map/generated/context.json
node spec/reliance-map/check.mjs \
spec/reliance-map/generated/graph.json \
--ledger spec/reliance-map/generated/correction-ledger.json \
--context spec/reliance-map/generated/context.json
The map does not capture dependencies automatically, establish that a correction is true, reverse an action, or authorize a consequence. The profile and its Handoff Admission parsing dependency are excluded from the installed package.
Experimental: bind recourse to witness equivocation
The source-only Witness Covenant profile binds a dedicated test-ledger allocation to one objective fault: two authentic, same-size checkpoints for the same log and epoch carry different roots. The verifier keeps checkpoint authenticity, equivocation, challenge state, capital, eligibility, authorization, and an attempted ledger event separate.
A bond changes recourse only. It does not make a receipt true, make the witness independent, establish custody or collection, or upgrade a provider claim.
PYTHONPATH=src python3 spec/witness-covenant/check.py \
spec/witness-covenant/vectors/fork-closed \
--context spec/witness-covenant/contexts/fork-closed.json
node spec/witness-covenant/check.mjs \
spec/witness-covenant/vectors/fork-closed \
--context spec/witness-covenant/contexts/fork-closed.json
Experimental: verify an answerability network
The source-only Answerability Network profile composes a constructed inference procurement, ActionReceipt, witnessed checkpoint, covenant, and 10,000-decision declared reliance graph. When the verifier receives two authentic incompatible checkpoint views, it identifies 2,500 declared descendants for recheck, leaves 5,000 complete unrelated decisions unaffected under the accepted graph, and keeps 2,500 incomplete branches unresolved.
Only the witness covenant's bounded remedy can become eligible. Provider claims and unrelated branches do not inherit the witness fault.
PYTHONPATH=src python3 spec/answerability-network/check.py \
spec/answerability-network/vectors/fork-closed \
--context spec/answerability-network/contexts/fork-closed.json
node spec/answerability-network/check.mjs \
spec/answerability-network/vectors/fork-closed \
--context spec/answerability-network/contexts/fork-closed.json
Where Bulla fits
- Payments: record the request, authorization, amount limits, supplied evidence, and dispute path.
- Permissions and writes: bind an operation to its stated principal and policy.
- Gateways and provider handoffs: retain the request and terms that crossed an organizational boundary.
ActionReceipt v0.2 remains the normative default. ActionReceipt v0.4 is an opt-in experimental draft. Source-only research profiles remain inspectable on GitHub but are excluded from the installed package unless the distribution policy explicitly releases them.
Limits
- File integrity does not establish that the reported action occurred or that every recorded field is true.
- A signature authenticates an accepted key; it does not create authority.
- Coverage is relative to the supplied receiver record.
- Bulla does not establish that the supplied receiver record is complete.
- Unsigned receipts remain unauthenticated.
- Reliance remains
NOT_COMPUTEDunless a reliance policy is supplied.
Multidimensional reports reject Boolean coercion. Callers inspect the named dimensions or apply an explicit reliance policy.
Documentation
- Quickstart
- Bulla documentation
- ActionReceipt standard
- Buyer requirements
- Ecosystem map
- Answerable Computing
- Status and evidence
- Complete capability reference
- Source-only experimental research
- Changelog
- Release lineage
- Security policy
License and security
Bulla is the developer toolkit. ActionReceipt is the open format implemented by Bulla. Glyph Standard, Inc. maintains the specification; Res Agentica contains the broader research program.
Bulla is licensed under the Apache License 2.0. Report vulnerabilities privately through GitHub Security Advisories or the security policy.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bulla-0.48.0.tar.gz.
File metadata
- Download URL: bulla-0.48.0.tar.gz
- Upload date:
- Size: 5.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
82d21b4346e8c41609988c875db7f79f337cd4995a043e7c41d8a1c000cac94c
|
|
| MD5 |
2c7ff8501894f0dd5cef020cfbfe270d
|
|
| BLAKE2b-256 |
2462a83622364f34ca0f32ca26e0d2daa1c12c043921625bb679131f029685d1
|
Provenance
The following attestation bundles were made for bulla-0.48.0.tar.gz:
Publisher:
publish.yml on jkomkov/bulla
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bulla-0.48.0.tar.gz -
Subject digest:
82d21b4346e8c41609988c875db7f79f337cd4995a043e7c41d8a1c000cac94c - Sigstore transparency entry: 2574917274
- Sigstore integration time:
-
Permalink:
jkomkov/bulla@e220a9340b063fa416ee97aaa28560d32a40b682 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/jkomkov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e220a9340b063fa416ee97aaa28560d32a40b682 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file bulla-0.48.0-py3-none-any.whl.
File metadata
- Download URL: bulla-0.48.0-py3-none-any.whl
- Upload date:
- Size: 797.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d74aaf2bcc73a0a145bdec0c0aca1b66b862a046f3ace174d1b56100cb33c169
|
|
| MD5 |
49e99fd48457de461dedfd98ff4a71e6
|
|
| BLAKE2b-256 |
b04afc99cb64c318ac2af4e0942f2f3593b0d3a53d0bd53673cf1f26b9f82b26
|
Provenance
The following attestation bundles were made for bulla-0.48.0-py3-none-any.whl:
Publisher:
publish.yml on jkomkov/bulla
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bulla-0.48.0-py3-none-any.whl -
Subject digest:
d74aaf2bcc73a0a145bdec0c0aca1b66b862a046f3ace174d1b56100cb33c169 - Sigstore transparency entry: 2574917314
- Sigstore integration time:
-
Permalink:
jkomkov/bulla@e220a9340b063fa416ee97aaa28560d32a40b682 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/jkomkov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e220a9340b063fa416ee97aaa28560d32a40b682 -
Trigger Event:
workflow_dispatch
-
Statement type: