Skip to main content

countersign-ai

Typed, LangGraph-native approval boundaries for consequential actions.

The PyPI distribution is countersign-ai; Python code imports countersign.

pip install countersign-ai
from countersign import require_approval


@require_approval(action="issue_refund", description="Approve this customer refund")
def issue_refund(order_id: str, amount_usd: float) -> str:
    return payments.refund(order_id, amount_usd)

Inside a LangGraph node or tool, the wrapper emits a Countersign v0 request via interrupt() and invokes the wrapped function only after an Agent Inbox-compatible accept or edit response. ignore and response raise ApprovalRejected, preventing the action from running accidentally.

The resume payload is untrusted

A decision arrives from outside the graph, so the wrapper enforces the contract the request published before the action can run. Each of these raises ApprovalContractError and executes nothing:

  • an accept when the request set allow_accept: false;
  • an edit when the request set allow_edit: false;
  • edited arguments that violate context.edit_schema (ApprovalEditValidationError, carrying one EditValidationIssue per failing field);
  • edited arguments the wrapped function cannot be called with.
from countersign import ApprovalContext, require_approval


@require_approval(
    action="issue_refund",
    context=ApprovalContext(
        edit_schema={
            "type": "object",
            "required": ["order_id", "amount_usd"],
            "additionalProperties": False,
            "properties": {
                "order_id": {"const": "ord_4821"},
                "amount_usd": {"type": "number", "exclusiveMinimum": 0, "maximum": 10_000},
            },
        }
    ),
)
def issue_refund(order_id: str, amount_usd: float) -> str:
    return payments.refund(order_id, amount_usd)

A request without context.edit_schema keeps the permissive Agent Inbox edit behavior, so existing graphs are unaffected. validate_approval_edit() applies the same rules outside the decorator, and matches validateApprovalEdit() in @countersign-ai/react so a reviewer sees the failure before submitting.

An edit schema constrains what a reviewer may change. It says nothing about the arguments the graph proposed, so keep the action's own limits for the accept path.

Interoperability

ApprovalRequest.model_validate() accepts an existing Agent Inbox HumanInterrupt unchanged. resume_command() maps a Countersign decision to the Command(resume=[HumanResponse]) shape LangGraph expects:

from countersign import ApprovalDecision, ApprovalRequest, resume_command

request = ApprovalRequest.model_validate(interrupt_payload)
command = resume_command(
    ApprovalDecision(type="edit", args={"amount_usd": 99}),
    request,
)
graph.invoke(command, config={"configurable": {"thread_id": "refund-4821"}})

Do not create a random request ID or timestamp inside an interrupted node. LangGraph reruns that node on resume; leave request_id and created_at absent, or pass stable values derived from graph state.

Adapters

SelfHostedLangGraphAdapter resumes a compiled in-process graph. Install the Platform transport extra for the REST/SSE adapter:

pip install 'countersign-ai[platform]'
from countersign.adapters import LangGraphPlatformAdapter

adapter = LangGraphPlatformAdapter(base_url=platform_url, api_key=api_key)
await adapter.resume(
    thread_id=thread_id,
    assistant_id=assistant_id,
    request=request,
    decision=decision,
)

Keep Platform credentials in the trusted server or worker using the adapter, never in browser code.

Develop

uv run --all-extras --group dev pytest -q
uv run --all-extras --group dev mypy src
uv build

The hosted queue, team authentication, billing, notification, and production persistence implementations are intentionally maintained in the private countersign-cloud repository.

Download files

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

Source Distribution

countersign_ai-0.1.0.tar.gz (12.0 kB view details)

Uploaded Source

Built Distribution

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

countersign_ai-0.1.0-py3-none-any.whl (11.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: countersign_ai-0.1.0.tar.gz
  • Upload date:
  • Size: 12.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for countersign_ai-0.1.0.tar.gz
Algorithm Hash digest
SHA256 95761fee64f8105399de8926523a75b7b0483a730d14418021af3c97f9838936
MD5 c46d24cff19c13f1b180e5490b794a44
BLAKE2b-256 5e09a0047ca8e2014ddc608434ff09aeaf36fcee93da8934c64b8e6aeebfef17

See more details on using hashes here.

Provenance

The following attestation bundles were made for countersign_ai-0.1.0.tar.gz:

Publisher: release-python.yml on starkyru/countersign

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

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

File metadata

  • Download URL: countersign_ai-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 11.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for countersign_ai-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ddcee8cc08dec482c1a37657e2216a631272cfb481ecd7af36e7e0e3e780c887
MD5 8b47f3a3bd0c32e7041a695bff5a0831
BLAKE2b-256 4b92353f8693827556ed332a0e50614f52d426ea5c98511ed377f9fa9ed5cde5

See more details on using hashes here.

Provenance

The following attestation bundles were made for countersign_ai-0.1.0-py3-none-any.whl:

Publisher: release-python.yml on starkyru/countersign

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page