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.13. 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 the following, after the workspace and the three joins at seq 0 to 3:

seq=4  task.create     agent:assistant#v1
seq=5  task.complete   agent:assistant#v1
seq=6  review.request  agent:assistant#v1   to=human:sam@example.org
seq=7  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.13
  • 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.

Metadata

Release files for chap-pydantic-ai 0.2.13

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for chap-pydantic-ai 0.2.13
File Size Uploaded
chap_pydantic_ai-0.2.13.tar.gz 14.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for chap-pydantic-ai 0.2.13
File Interpreter ABI Platform
chap_pydantic_ai-0.2.13-py3-none-any.whl Python 3 none any Details

Total release size: 26.4 kB

Release files / chap_pydantic_ai-0.2.13.tar.gz

Download URL chap_pydantic_ai-0.2.13.tar.gz
Size 14.3 kB
Tags Source
SHA-256 checksum
How to use checksums
fd8b28a26d5f171ec340f2f850460590fc1b48037d96305489bc75b2db3a09d0
BLAKE2b-256 checksum
How to use checksums
dd3b29db9eb06f9f65b4048b858bcaadc373fa0c88d2f30499ecaca025912edf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.6

Release files / chap_pydantic_ai-0.2.13-py3-none-any.whl

Download URL chap_pydantic_ai-0.2.13-py3-none-any.whl
Size 12.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
836d2a957e455da5b3fe6637a01e230c39d305acbc9ae20794ff13c26dc8f31c
BLAKE2b-256 checksum
How to use checksums
9dd74911055890e3c00a5c39a9e1b7f0ffb80f8b38669819277ca117bf11cdd3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.9.6

Release history Release notifications | RSS feed

This release

0.2.13 This release

2 release files

0.2.10

2 release files

0.2.9

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page