Skip to main content

chap-pydantic-ai

Adapter between Pydantic AI and the CHAP Coordinator. When a run pauses for tool approval, the human's decision -- approve, edit the arguments, or deny -- becomes a hash-linked, replayable CHAP audit entry.

Pydantic AI resolution            CHAP envelope
------------------------          ---------------------------
True / ToolApproved()             decide.approve
ToolApproved(override_args=...)   decide.override   (diff of the args)
False / ToolDenied(message=...)   decide.reject

The proposed tool call (tool name, validated args, tool_call_id) is the artefact under review; the resolution is the human's decision on it. An edited argument is recorded as an RFC 6902 diff, so the chain captures what changed and why, not just approved/denied.

Install

pip install chap-pydantic-ai

Depends on chap-coordinator>=0.2.9. Pydantic AI is optional: the adapter reads the resolution objects structurally, so the bridge and its tests work without it installed. Install the extra to run a live agent:

pip install "chap-pydantic-ai[pydantic-ai]"

Quick start

Pydantic AI surfaces human-in-the-loop through deferred tools. A tool marked requires_approval=True ends the run with a DeferredToolRequests output; you resolve each pending call into a DeferredToolResults and feed it back via deferred_tool_results=. The adapter records the decision at that resolution point:

from pydantic_ai import Agent, DeferredToolRequests, DeferredToolResults, ToolApproved
from chap_coordinator import Coordinator
from chap_pydantic_ai import ChapApprovalBridge

agent = Agent("openai:gpt-4o", output_type=[str, DeferredToolRequests])

@agent.tool_plain(requires_approval=True)
def transfer(amount: int, to: str) -> str:
    return f"sent {amount} to {to}"

bridge = ChapApprovalBridge(
    Coordinator(),
    workspace="wsp_payments",
    agent="agent:assistant#v1",
    reviewer="human:alice@example.org",
)

result = agent.run_sync("pay the invoice")
if isinstance(result.output, DeferredToolRequests):
    requests = result.output

    # The human resolves each pending approval.
    results = DeferredToolResults()
    call = requests.approvals[0]
    results.approvals[call.tool_call_id] = ToolApproved(
        override_args={**call.args_as_dict(), "amount": 50},
    )

    # Record the decision, then let the agent finish.
    bridge.record_results(requests, results)
    result = agent.run_sync(
        "pay the invoice",
        message_history=result.all_messages(),
        deferred_tool_results=results,
    )

record_results walks requests.approvals, pairs each with its entry in results.approvals, and records one CHAP decision per call. The reviewer identity, rationale, tags, and the refine-vs-replace signal ride in results.metadata[tool_call_id]:

results.metadata = {call.tool_call_id: {
    "approver":         "human:sam@example.org",
    "rationale":        "over the desk limit; capped to 50",
    "tags":             ["limit-exceeded"],
    "intent_preserved": True,   # same decision, smaller amount
}}

To record a single decision directly, skip record_results and call record_decision(call, resolution, **signal).

intent_preserved

Editing arguments defaults to a refining override (intent_preserved=true): the human kept the decision and changed the inputs. That default is not always right -- sometimes an edit is a different decision in disguise -- so the reviewer can set intent_preserved explicitly through the metadata channel and the adapter records what they say.

Approver identity

A decision record is only useful if it names who decided. CHAP has no ambient actor: the decider is whatever from the envelope carries. The bridge uses its reviewer by default, but a per-decision approver (set on the call or in results.metadata) overrides it, and the adapter joins that approver to the workspace before recording.

What you get in the audit chain

One run with an edited approval yields:

seq=3  task.create     agent:assistant#v1
seq=4  task.complete   agent:assistant#v1
seq=5  review.request  agent:assistant#v1   to=human:sam@example.org
seq=6  decide.override human:sam@example.org  diff=[{op:replace, path:/args/amount, value:50}]

Every entry carries prev_hash, so the chain verifies externally or anchors to a SCITT transparency service with the audit-scitt/1.0 profile.

Example

examples/01-approve-edit-deny.py drives one approval-gated tool through all three decisions using Pydantic AI's TestModel (offline, no API key) and prints the resulting chain.

Compatibility

  • chap-coordinator 0.2.9
  • pydantic-ai 1.x–2.x (optional; verified against 2.0)
  • Python 3.10, 3.11, 3.12, 3.13

License

Apache 2.0. See LICENSE.

Download files

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

Source Distribution

chap_pydantic_ai-0.2.9.tar.gz (14.4 kB view details)

Uploaded Source

Built Distribution

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

chap_pydantic_ai-0.2.9-py3-none-any.whl (12.1 kB view details)

Uploaded Python 3

File details

Details for the file chap_pydantic_ai-0.2.9.tar.gz.

File metadata

  • Download URL: chap_pydantic_ai-0.2.9.tar.gz
  • Upload date:
  • Size: 14.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.15

File hashes

Hashes for chap_pydantic_ai-0.2.9.tar.gz
Algorithm Hash digest
SHA256 b6756764896eca56a15f607ebe026428b98761e4deaaf1633d2898d99fb9eb0b
MD5 1e0c3da02f605f6b0b53107d181267e4
BLAKE2b-256 caa3be91a95d8e35606920d544210c2ca8209602af93dd2bb41ed11fc7bbfe11

See more details on using hashes here.

File details

Details for the file chap_pydantic_ai-0.2.9-py3-none-any.whl.

File metadata

File hashes

Hashes for chap_pydantic_ai-0.2.9-py3-none-any.whl
Algorithm Hash digest
SHA256 fd43275e0e910fa9266282936bd9bc05963b3204e0c14ac8cecf65c027545208
MD5 e41d660d6d056f637e5ecc4f0e9a3226
BLAKE2b-256 4f89b297df8f318627a91f8a5473af825fc1685ee5404625e0beb21da419ad13

See more details on using hashes here.

Supported by

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