Conseqa for Python
Your agent calls a provider and gets a reply, or does not. Conseqa records what the call was meant to do before it is made, then reads the system of record afterwards to establish what is actually true: verified, failed, or honestly unknown.
This package is the application side for Python programs. It has no dependencies.
pip install conseqa
It needs Python 3.9 or newer, and a Conseqa runner, version 0.1.8 or newer, to talk to. The
runner and the tool that builds contracts are Node.js programs; the guide at
https://www.conseqa.dev/docs sets them up, and npx @conseqa/cli quickstart gives you both
running in two minutes.
Protect a call
This protects the refund that the quickstart sets up. create_refund stands for the call your
program already makes.
import os
from conseqa import Conseqa, ambiguous, load_contract_package, rejected
def what_an_error_proves(error):
# A refusal with a 4xx status proves the refund was not made.
# Anything else, a dropped connection above all, proves nothing either way.
status = getattr(error, "status", None)
if isinstance(status, int) and 400 <= status < 500:
return rejected(f"http_{status}")
return ambiguous("no_reply")
package = load_contract_package("dist/conseqa")
refund_processed = package.contract(
"quickstart.refund.processed",
1,
# What identifies the thing that was made, taken from the service's reply.
bind_result=lambda reply: {"refundId": reply["id"]} if reply.get("id") else None,
classify_exception=what_an_error_proves,
)
conseqa = Conseqa(
package,
runner_url="http://127.0.0.1:4319",
sidecar_token=os.environ.get("CONSEQA_SIDECAR_TOKEN"),
)
intent = {"paymentId": "pay_1001", "amountMinor": 2500, "currency": "USD"}
reply = conseqa.protect(
refund_processed,
action_key="ticket-8042-refund",
intent=intent,
# The call you already make, given the reference to send with it.
execute=lambda call: create_refund({**intent, "reference": call.provider_correlation_key}),
)
protect returns what your call returned and raises what it raised. In between it tells
the runner what was intended, that the call is starting, and how it ended. The runner then
reads your system with its own read-only credential and decides the verdict, which appears
in the dashboard.
action_keyis one stable key for the business operation, such as the ticket or order it belongs to. A key that was already used raisesDuplicateActionErrorand the call is not made.- With the default
mode="required", the call is not made unless the intent was recorded first; a runner that cannot be reached raisesProtectionUnavailableError. Withmode="best_effort"the call goes ahead anyway and shows as an unprotected gap. intentis what the call is meant to do, in the shape of the contract's intent schema.
The contract
A contract package is built once with conseqa contracts build and installed in the runner.
Its runner half, which finds and reads the record, runs inside the runner and stays as it
is. Its application half is three small decisions, and a Python program makes them in
Python, as arguments to package.contract:
| Argument | What it decides | Left out |
|---|---|---|
bind_result |
What identifies the record, from the call's reply. Return None when the reply does not say. |
Required |
classify_exception |
What an exception proves: rejected(...), not_executed(...) or ambiguous(...). |
Every exception is ambiguous, the safe reading. |
correlation_key |
The reference sent with the call, so that a record whose reply was lost can be found by an exact match afterwards. | cq_ and 24 random hex digits |
load_contract_package hashes the package's files again and compares the result with its
manifest, so a program never registers an intent against a build other than the one on disk.
Connectors
For a provider Conseqa has a connector for, the application half is already written.
conseqa.connectors gives it in Python, and helps build the request so that the key goes
where the runner will look for it. Build the contract package from the JavaScript
connector as usual; its runner half reads the provider.
| Provider | Contract (from the package) | Request helper | Where the key goes |
|---|---|---|---|
| Stripe refunds | stripe_refund(package) |
decorate_stripe_refund |
refund metadata and the idempotency key |
| Razorpay refunds | razorpay_refund(package) |
decorate_razorpay_refund |
the refund's receipt and X-Refund-Idempotency |
| Shopify refunds | shopify_refund(package) |
decorate_shopify_refund |
the refund's note and @idempotent |
| Cashfree refunds | cashfree_refund(package) |
decorate_cashfree_refund |
the refund's own refund_id |
| RazorpayX payouts | razorpayx_payout(package) |
decorate_razorpayx_payout |
the payout's reference_id and its idempotency key |
import requests
from conseqa.connectors import cashfree_refund, decorate_cashfree_refund
refund = cashfree_refund(package)
intent = {"orderId": "order_8042", "amountMinor": 249950, "currency": "INR"}
def create_refund(call):
request = decorate_cashfree_refund(
intent, call.provider_correlation_key, {"refund_note": "Damaged on arrival"}
)
response = requests.post(
"https://api.cashfree.com/pg" + request["path"],
json=request["body"],
headers={**request["headers"], **cashfree_credentials},
timeout=10,
)
response.raise_for_status()
return response.json()
reply = conseqa.protect(
refund, action_key="ticket-8042-refund", intent=intent, execute=create_refund
)
Each connector's exception rules read the shapes Python clients raise: http_status on
stripe-python's errors, response.status_code on those of requests and httpx. Raise
conseqa.connectors.NotDispatchedError only when your code can prove a request never
left the program; anything else is treated as possibly done, and the runner looks.
asyncio
reply = await conseqa.protect_async(
refund_processed,
action_key="ticket-8042-refund",
intent=intent,
execute=create_refund_async,
)
execute may be a coroutine function. The requests to the runner are made off the event
loop. A call that is cancelled part way is reported as ambiguous, because it is.
Traces and coverage
If your program uses OpenTelemetry, each protected call gets a span in the trace that is
active, marked so that the dashboard can show the action inside the agent run that made it
and count coverage. Nothing needs configuring: installing opentelemetry-api is enough, and
pip install "conseqa[tracing]" does that. To count a run that was not protected, wrap it:
conseqa.observe("refund.create", lambda: create_refund(refund))
What it raises
| Error | What happened |
|---|---|
ProtectionUnavailableError |
A required action could not be recorded first, so the call was not made. |
DuplicateActionError |
The action key was already used, so the call was not made. Carries the earlier ids. |
RunnerUnavailableError |
The runner did not answer. The cause of a ProtectionUnavailableError. |
RunnerRejectedError |
The runner refused the request. Carries the status and what it replied. |
ContractPackageIntegrityError |
The built contract package does not match its manifest. Build it again. |
UnregisteredContractError |
The contract did not come from the package this client was given. |
Everything your own call raises is raised to you unchanged.
What this version does not do
There is no spool: when the runner cannot be reached, a required action stops and a best-effort one runs unprotected. The JavaScript SDK can hold intents in an encrypted folder shared with the runner; this one cannot yet.
Apache-2.0 licensed. Questions: https://www.conseqa.dev/docs.
Metadata
Release files for conseqa 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| conseqa-0.1.0.tar.gz | 28.6 kB | With provenance | Details for conseqa-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| conseqa-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for conseqa-0.1.0-py3-none-any.whl |
Total release size: 58.0 kB
Release files / conseqa-0.1.0.tar.gz
| Download URL | conseqa-0.1.0.tar.gz |
|---|---|
| Size | 28.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cf2d84960a697e5924777d78ff794477a513ff95af4696e4f863a8cb18530316
|
|
BLAKE2b-256 checksum How to use checksums |
be5fdcf36f8616693c56d8b0937c6e8ddbf05cf8a6b4635504cacb053ebe2c1c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logRelease files / conseqa-0.1.0-py3-none-any.whl
| Download URL | conseqa-0.1.0-py3-none-any.whl |
|---|---|
| Size | 29.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8f0184da47766bba31a93dcd36fa57542f7b33995b1b8186863ff9a3a8c78829
|
|
BLAKE2b-256 checksum How to use checksums |
033f13a97fb6c578d965635494787ccdcc120aa954b4deddbb7d11b89de46a38
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logSecurity for conseqa 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for conseqa-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.