Skip to content

Python: Add spend preflight receipt middleware sample#7116

Open
jw-ond wants to merge 2 commits into
microsoft:mainfrom
jw-ond:codex/action-bound-tool-approval-sample
Open

Python: Add spend preflight receipt middleware sample#7116
jw-ond wants to merge 2 commits into
microsoft:mainfrom
jw-ond:codex/action-bound-tool-approval-sample

Conversation

@jw-ond

@jw-ond jw-ond commented Jul 14, 2026

Copy link
Copy Markdown

Motivation & Context

This adds a Python middleware sample for the paid-tool preflight and receipt scenario described in #7078.

The scenario is useful for agents that may call tools with external cost or quota impact. In those cases, the application needs a checkpoint that can evaluate a canonical spend envelope before the tool body runs, deny or require approval before side effects occur, and record an execution receipt afterward.

Description & Review Guide

  • What are the major changes?

    • Adds spend_preflight_receipt_middleware.py under the Python middleware samples.
    • Updates the middleware sample README with the new sample.
    • Demonstrates a middleware-owned authorization checkpoint before call_next().
    • Records a typed receipt for approved, denied, approval-required, failed, and duplicate execution paths.
    • Uses an execution-claim guard so a repeated tool call cannot spend twice.
  • What is the impact of these changes?

    • Documentation/sample only. No framework API or runtime behavior changes.
    • No new dependency is added.
  • What do you want reviewers to focus on?

    • Whether the sample belongs under python/samples/02-agents/middleware/.
    • Whether the spend envelope and receipt names are clear enough for users adapting this pattern.
    • Whether the middleware should demonstrate a different termination/result style for approval-required actions.

Related Issue

Fixes #7078

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

Validation

  • python3 -m py_compile python/samples/02-agents/middleware/spend_preflight_receipt_middleware.py
  • git diff --check HEAD~1..HEAD

Note: I also tried uv run poe pyright -S, but uv is not installed in this local environment.

Copilot AI review requested due to automatic review settings July 14, 2026 17:10
@giles17 giles17 added documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python labels Jul 14, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Python middleware sample demonstrating a “paid tool” execution boundary pattern (preflight authorization + post-execution receipt) for agents that call tools with external spend/quota side effects, aligning with the scenario requested in #7078.

Changes:

  • Introduces SpendPreflightReceiptMiddleware, which builds a spend envelope, authorizes before call_next(), blocks non-approved calls, and records receipts.
  • Demonstrates a guarded execution-claim approach intended to prevent duplicate spend for the same tool call.
  • Updates the middleware samples README to list the new sample.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
python/samples/02-agents/middleware/spend_preflight_receipt_middleware.py New sample implementing spend envelope preflight + receipt recording around a paid tool.
python/samples/02-agents/middleware/README.md Adds the new sample to the middleware samples index.

Comment on lines +91 to +108
def build_spend_envelope(context: FunctionInvocationContext) -> tuple[SpendEnvelope, str]:
"""Build the envelope and return it with its stable hash."""
arguments = arguments_to_dict(context.arguments)
args_hash = sha256(canonical_json(arguments))
call_id = context.metadata.get("call_id")
issued_at = datetime.now(timezone.utc)
envelope = SpendEnvelope(
function_name=context.function.name,
call_id=call_id if isinstance(call_id, str) and call_id else f"direct:{args_hash[:12]}",
args_hash=args_hash,
amount_usd=str(arguments.get("amount_usd", "0")),
payee=str(arguments.get("payee", "unknown")),
resource=str(arguments.get("dataset_name", "unknown")),
policy_version=POLICY_VERSION,
expires_at=(issued_at + timedelta(minutes=5)).isoformat(),
)
return envelope, sha256(canonical_json(envelope.to_dict()))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3b384bb. The authorization hash now uses only stable envelope fields via authorization_dict(), while expires_at remains in the full envelope and receipts for audit context.

Comment on lines +118 to +127
amount = float(envelope.amount_usd)
if amount > 100:
verdict = "denied"
reason = "amount exceeds the sample hard limit"
elif amount > 50:
verdict = "requires_approval"
reason = "amount exceeds the sample auto-approval threshold"
else:
verdict = "approved"
reason = "amount is inside the sample auto-approval threshold"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3b384bb. authorize_spend now denies non-finite and negative amounts before applying the sample thresholds.

def buy_dataset_access(
dataset_name: Annotated[str, Field(description="The dataset to purchase access for.")],
payee: Annotated[str, Field(description="The provider receiving payment.")],
amount_usd: Annotated[float, Field(description="The spend amount in USD.")],

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3b384bb. amount_usd now has a non-negative finite Field constraint so invalid values are rejected at validation before middleware logic.

@jw-ond

jw-ond commented Jul 14, 2026

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree company="Ond Holdings Inc."

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Feature]: Sample function middleware for paid tool preflight and receipt

3 participants