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.

Metadata

Release files for chap-pydantic-ai 0.2.10

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.10
File Size Uploaded
chap_pydantic_ai-0.2.10.tar.gz 14.3 kB Details

Built distribution (wheel)

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

Total release size: 26.4 kB

Release files / chap_pydantic_ai-0.2.10.tar.gz

Download URL chap_pydantic_ai-0.2.10.tar.gz
Size 14.3 kB
Tags Source
SHA-256 checksum
How to use checksums
d6ded212e859f393b2641452822a8b0eb7ea67e891111fcdd5759412edeedaa3
BLAKE2b-256 checksum
How to use checksums
86e368f2227e16a81a78bc59686a3f3fdf2efa7384c16f09862016ec2a0d7aaa
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.10-py3-none-any.whl

Download URL chap_pydantic_ai-0.2.10-py3-none-any.whl
Size 12.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f339c12283a9861b9cdcda3c9c6a56c3876da7e8aa3a1073499234540ce97bee
BLAKE2b-256 checksum
How to use checksums
4600f315db753571247ff96f28be4a17a7ebc57ec7babc2f8eee9ed66f92571d
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.10 This release

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