Description
Problem
Agents that call tools which move money (POs, refunds, paid APIs) need an authorize → execute → evidence path outside the LLM. Agent Framework already has function middleware for intercepting tool calls; a sample would show how to wire that hook to an external spend-authorization layer.
Proposal
Add a focused Python sample under python/samples/02-agents/ (middleware/tools) that:
- Registers a paid tool (e.g.
submit_po / issue refund)
- Uses function middleware to preflight authorization before the tool body runs
- On allow: runs the tool, then submits completion evidence / receipt
- On deny: returns an error to the agent without executing the side effect
Dependencies via PEP 723 inline script metadata (e.g. paybond-kit), not added to the root pyproject.toml — matching SAMPLE_GUIDELINES.md.
Why here
Complements existing middleware samples (logging/guardrails) with a concrete “economic side effect” pattern. Does not propose a first-party package under packages/ — sample-only unless maintainers prefer a different placement.
Alternatives considered
- Standalone external repo only (fine as fallback if in-tree third-party deps are unwanted)
- Agent-run middleware instead of function middleware (too coarse for per-tool spend)
Willingness
I can implement and open a PR against main following SAMPLE_GUIDELINES.md and CONTRIBUTING.md once maintainers confirm folder placement.
References
Code Sample
import asyncio
import os
from collections.abc import Awaitable, Callable, Mapping
from typing import Annotated, Any
from uuid import uuid4
from agent_framework import Agent, FunctionInvocationContext, tool
from agent_framework.foundry import FoundryChatClient
from azure.identity.aio import AzureCliCredential
from pydantic import BaseModel, Field
from paybond_kit import Paybond
from paybond_kit.agent.registry import create_paybond_tool_registry
from paybond_kit.spend_guard import PaybondSpendApprovalRequiredError, PaybondSpendDeniedError
@tool(approval_mode="never_require")
def submit_po(
amount_cents: Annotated[int, Field(description="PO amount in cents.")],
) -> str:
"""Submit a purchase order."""
return f'{{"status":"completed","cost_cents":{amount_cents},"po_id":"po-{amount_cents}"}}'
def _as_mapping(arguments: BaseModel | Mapping[str, Any]) -> dict[str, Any]:
if isinstance(arguments, BaseModel):
return arguments.model_dump()
return dict(arguments)
async def main() -> None:
paybond = await Paybond.open(api_key=os.environ["PAYBOND_API_KEY"])
run = await paybond.agent_run.bind(
{
"bootstrap": {
"kind": "sandbox",
"operation": "submit_po",
"requested_spend_cents": 12000,
"completion_preset": "cost_and_completion",
},
"registry": create_paybond_tool_registry(
{
"default_deny": True,
"side_effecting": {
"submit_po": {
"operation": "submit_po",
"evidence_preset": "cost_and_completion",
"spend_cents": lambda args: int(args["amount_cents"]),
}
},
}
),
}
)
async def paybond_spend_gate(
context: FunctionInvocationContext,
call_next: Callable[[], Awaitable[None]],
) -> None:
tool_call_id = str(context.metadata.get("call_id") or uuid4())
async def execute() -> object:
await call_next()
return context.result
try:
wrapped = await run.interceptor.wrap_execute(
tool_name=context.function.name,
tool_call_id=tool_call_id,
arguments=_as_mapping(context.arguments),
execute=execute,
)
context.result = wrapped.tool_result
except (PaybondSpendDeniedError, PaybondSpendApprovalRequiredError) as exc:
context.result = str(exc)
async with (
AzureCliCredential() as credential,
Agent(
client=FoundryChatClient(credential=credential),
name="ProcurementAgent",
instructions="Submit POs only within policy.",
tools=submit_po,
middleware=[paybond_spend_gate],
) as agent,
):
result = await agent.run("Submit a PO for $120")
print(result.text)
if __name__ == "__main__":
asyncio.run(main())
Language/SDK
Python
Description
Problem
Agents that call tools which move money (POs, refunds, paid APIs) need an authorize → execute → evidence path outside the LLM. Agent Framework already has function middleware for intercepting tool calls; a sample would show how to wire that hook to an external spend-authorization layer.
Proposal
Add a focused Python sample under
python/samples/02-agents/(middleware/tools) that:submit_po/ issue refund)Dependencies via PEP 723 inline script metadata (e.g.
paybond-kit), not added to the rootpyproject.toml— matching SAMPLE_GUIDELINES.md.Why here
Complements existing middleware samples (logging/guardrails) with a concrete “economic side effect” pattern. Does not propose a first-party package under
packages/— sample-only unless maintainers prefer a different placement.Alternatives considered
Willingness
I can implement and open a PR against
mainfollowing SAMPLE_GUIDELINES.md and CONTRIBUTING.md once maintainers confirm folder placement.References
Code Sample
Language/SDK
Python