Skip to content
Browse the documentation
SDKs

Python SDK

Record agent actions, push facts from systems of record, read verdicts and draft contracts. Standard library only; Python 3.10+.

Last changed Markdown versionllms.txt

Record agent actions, push facts from systems of record, read verdicts and draft contracts. Standard library only; Python 3.10+.

pip install ./sdk/python          # from this repository
export COMMITLAYER_API_KEY=spk_...          # the agent runtime's key: a *recorder* key (records actions, nothing else)
export COMMITLAYER_EVIDENCE_KEY=spk_...     # the pipeline's key: an *evidence* key bound to the sources it pushes for
# COMMITLAYER_API_URL defaults to https://api.commitlayer.ai. One credential per side of the line: the key that
# records an action can never also supply the facts that verify it.

Record an action where it happens

from commitlayer import Client

cl = Client(agent_id="support-bot")


@cl.action(
    "refund_workflow",
    subject=lambda order_id, **_: order_id,
    params=lambda amount, **_: {"amount": amount},
    subject_kind="order",
    action_id=lambda order_id, **_: f"refund:{order_id}",  # retries update one claim instead of recording two
)
def refund(order_id: str, amount: float) -> dict:
    return payments.refund(order_id, amount)  # the agent's own work, unchanged


refund("order_10231", 180.0)
print(refund.last_recorded["verification"]["summary"]["headline"])

The action is recorded before the function runs, so its request time precedes every effect. The function's return value is never changed and never consulted: verification reads the systems of record. If the API cannot be reached the function still runs and a warning is logged (fail_open=True); pass fail_open=False to raise.

Push facts, with a watermark

Use an evidence key here, bound to the sources it pushes for (Settings → API keys). The key your agent runtime records actions with is a recorder key and cannot push facts: the credential that records an action never also supplies the facts that verify it.

import os

evidence = Client(api_key=os.environ["COMMITLAYER_EVIDENCE_KEY"])  # the pipeline's own client, not the runtime's

evidence.push_facts(
    [
        {
            "source": "crm",
            "subject_id": "order_10231",
            "fact_type": "note_added",
            "occurred_at": "2026-10-13T10:42:09Z",
        }
    ],
    watermarks={"crm": "2026-10-13T10:45:00Z"},  # everything up to here has been sent for crm
)

A verdict that is not yet a PASS settles only when its window has closed and every source it reads has a watermark past the end of the window. Declare one when your feed lags; a source that never declares one is taken as current.

Draft a contract from what was recorded

d = cl.draft_contract("refund_order", lookback_days=30)
print(d["yaml"])  # expected effects, window, preconditions, limits, from the sample
print(d["stats"]["effects"])
v = cl.validate_contract(d["yaml"])  # errors, and weakening warnings against the version it would replace
cl.save_contract(d["yaml"])  # the response carries the same warnings

Every value in a draft is what the sample showed, not what the policy says. Review it before saving.

Read a verdict

v = cl.get_action("refund:order_10231")
v["status"]  # pending | settled
v["settlement"]["waiting_on"]  # sources still behind the window, if any
v["reversal"]  # the later record that undid the action, if any
v["sources"]  # trust level and watermark per source the contract read

Wait for the verdict before saying "done"

v = cl.wait_for_settlement("refund:order_10231", timeout=300)  # blocks until settled or 5 minutes
if v["timed_out"]:
    ...  # still pending: your policy, never "success"
elif v["lenses"]["outcome"]["verdict"] == "PASS":
    notify_customer("Your order is cancelled and refunded.")
else:
    open_recovery(v)  # FAIL: the reasons name what is missing or forbidden

The API holds each call for up to 60 seconds; the SDK loops. The gate reads evidence; it never changes a verdict.

Correct a failed action

cl.record_action(
    "refund_workflow",
    "order_10231",
    params={"amount": 180.0},
    action_id="refund:order_10231:fix",
    compensates="refund:order_10231",
)

Once the correction settles PASS, the failed action carries resolution.state == "compensated"; its own verdict never changes.