{ "cells": [ { "cell_type": "markdown", "id": "overview", "metadata": {}, "source": [ "# Build an AI agent that can pay for APIs using AgentCore Payments\n", "\n", "AI agents often need information from external services to complete a task. A procurement agent might check a supplier against a sanctions database, retrieve a current risk report, or use a paid search API. When those services charge for each request, the application needs clear rules for which purchases the agent can request and who approves the spending.\n", "\n", "Agentic commerce describes workflows in which an agent can request a paid resource while application code retains authority over payment. Amazon Bedrock AgentCore Payments provides infrastructure for bounded payment sessions, and the OpenAI Agents SDK lets a model request an application-defined tool. The application checks the merchant, purpose, amount, and approval before a payment can occur.\n", "\n", "This notebook explains that workflow through a supplier research example. It covers the AgentCore Payments flow, the x402 payment exchange, application-owned spending controls, and receipt verification. The same payment controls are first illustrated with a local simulation and then connected to AgentCore Payments on the AWS testnet.\n" ] }, { "cell_type": "markdown", "id": "business-scenario", "metadata": {}, "source": [ "## Why agents need access to paid services\n", "\n", "Consider a procurement agent reviewing a fictional supplier named Northstar Components. The agent has basic supplier information, but it needs a current risk report from a paid API before it can summarize potential concerns for a human reviewer.\n", "\n", "The application approves that data provider, defines a supplier research purpose, and sets a maximum price for the report. It also records the human approval associated with the request. When the model decides that the report is useful, it requests the `x402_fetch` tool with the supplier report URL and the approved purpose.\n", "\n", "The merchant returns an HTTP `402 Payment Required` challenge describing its payment terms. The application checks those terms against the approved merchant, amount, network, expiry, and budget. If the request passes, the application obtains a bounded payment proof, retrieves the report, and records a receipt and audit history. The agent then prepares its answer from the report and receipt evidence returned by the tool.\n" ] }, { "cell_type": "markdown", "id": "authority-boundaries", "metadata": {}, "source": [ "## Architecture and payment authority\n", "\n", "The system separates four responsibilities:\n", "\n", "1. **The AI agent** identifies when a paid service could help complete the user's request and calls the application-defined tool.\n", "2. **The application** checks the merchant, purchase purpose, amount, approval, and spending limit before recording the receipt and audit history.\n", "3. **AgentCore Payments** generates a payment proof within the limits of an approved payment session.\n", "4. **The paid API** checks the payment proof and returns the requested content.\n", "\n", "The local example simulates the AgentCore Payments step. The connected AWS workflow uses AgentCore Payments to generate the bounded proof.\n", "\n", "The application is the boundary between the model and the payment infrastructure. The model receives the resource URL and research purpose, while approval records, spending limits, payment credentials, and audit state remain outside its control.\n", "\n", "![An agent requests a paid supplier report through application controls and AgentCore Payments](../../../../images/partners/AWS/controlled-agentic-commerce-overview.png)\n", "\n", "*The application controls approval and spending, while AgentCore Payments provides a bounded payment session for the connected AWS workflow.*\n" ] }, { "cell_type": "markdown", "id": "prerequisites", "metadata": {}, "source": [ "## 1. Setting up the supplier research example\n", "\n", "The example requires Python 3.11 through 3.13 and `uv`. Follow the repository-root Jupyter command in `README.md` so the notebook can load its package and display the architecture diagrams.\n", "\n", "The first part uses a scripted model, a simulated merchant, and a synthetic payment proof. This setup lets you inspect the same request, approval, receipt, and audit sequence without configuring AWS credentials or transferring funds. The final section shows where a real Amazon Bedrock model and AgentCore Payments testnet resources enter the workflow.\n", "\n", "The following cell locates the example package and makes its application, merchant, and policy modules available to the notebook.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "find-root", "metadata": {}, "outputs": [], "source": [ "import sys\n", "from pathlib import Path\n", "\n", "cwd = Path.cwd().resolve()\n", "candidates = [\n", " cwd,\n", " cwd / \"examples/partners/AWS/controlled_agentic_commerce_with_agentcore_payments\",\n", " *cwd.parents,\n", "]\n", "ROOT = next(\n", " (path for path in candidates if (path / \"src/agentic_commerce\").is_dir()),\n", " None,\n", ")\n", "if ROOT is None:\n", " raise FileNotFoundError(\"Run this notebook from its example directory.\")\n", "sys.path.insert(0, str(ROOT / \"src\"))\n", "\n", "print(f\"Example root: {ROOT}\")" ] }, { "cell_type": "markdown", "id": "policy-intro", "metadata": {}, "source": [ "## 2. Defining supplier requests and spending limits\n", "\n", "The application needs to establish its payment rules before the model can request a tool. In this example, the supplier report costs 0.25 synthetic USDC. The application also defines the approved merchant, the permitted research purpose, a maximum amount for this purchase, and the total budget available to the agent run.\n", "\n", "`ApprovalGrant` ties the purchase to a particular request ID and resource URL. Its expiry limits how long that authorization remains valid. The model never supplies or changes these values; they are provided by application code when the tool is created.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "build-policy", "metadata": {}, "outputs": [], "source": [ "from datetime import UTC, datetime, timedelta\n", "from decimal import Decimal\n", "\n", "from agentic_commerce.demo import build_demo\n", "from agentic_commerce.merchant import RESOURCE_URL\n", "from agentic_commerce.models import ApprovalGrant\n", "\n", "now = datetime.now(UTC)\n", "application, merchant, local_payments = build_demo(now)\n", "approval = ApprovalGrant(\n", " approval_id=\"approval-cookbook-001\",\n", " request_id=\"request-cookbook-001\",\n", " resource_url=RESOURCE_URL,\n", " purpose=\"supplier_due_diligence\",\n", " maximum_amount=Decimal(\"0.25\"),\n", " approved_by=\"synthetic-cookbook-reviewer\",\n", " approved_at=now,\n", " expires_at=now + timedelta(minutes=10),\n", ")\n", "\n", "{\n", " \"approved_merchant\": str(RESOURCE_URL),\n", " \"purpose\": approval.purpose,\n", " \"approved_amount\": str(approval.maximum_amount),\n", " \"run_budget\": str(application.policy.policy.per_run_limit),\n", " \"currency\": approval.currency,\n", " \"approval_expires_at\": approval.expires_at.isoformat(),\n", "}" ] }, { "cell_type": "markdown", "id": "runner-intro", "metadata": {}, "source": [ "## 3. Defining an agent tool for approved purchases\n", "\n", "The `x402_fetch` function is the only payment-related tool available to the agent. The model can provide a resource URL and a business purpose, but the application supplies the request ID, idempotency key, spending policy, and approval grant.\n", "\n", "Before starting a purchase, the tool consumes a one-use capability. That check prevents a model from issuing multiple economic requests during the same agent run. `application.purchase` then validates the merchant and payment challenge before producing a receipt.\n", "\n", "`AgentPurchaseEvidence` returns only the report and receipt fields the model needs to prepare its answer. Wallet material, payment proof headers, and the complete application audit trail are kept out of the model context.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "define-agent-tool", "metadata": {}, "outputs": [], "source": [ "from threading import Lock\n", "\n", "from agents import Agent, ModelSettings, function_tool\n", "from openai.types.shared import Reasoning\n", "\n", "from agentic_commerce.agent import PurchaseResultRecorder, SupplierResearchOutput\n", "from agentic_commerce.errors import AgentResultInvalid\n", "from agentic_commerce.learning_model import ScriptedCommerceLearningModel\n", "from agentic_commerce.models import AgentPurchaseEvidence, PurchaseRequest\n", "\n", "recorder = PurchaseResultRecorder()\n", "model = ScriptedCommerceLearningModel()\n", "tool_capability = {\"used\": False}\n", "tool_lock = Lock()\n", "\n", "\n", "@function_tool\n", "def x402_fetch(resource_url: str, purpose: str) -> str:\n", " \"\"\"Fetch one paid resource using application-owned approval.\"\"\"\n", "\n", " with tool_lock:\n", " if tool_capability[\"used\"]:\n", " raise AgentResultInvalid(\n", " \"access_count_invalid\",\n", " \"The economic tool permits exactly one invocation.\",\n", " )\n", " tool_capability[\"used\"] = True\n", "\n", " purchase = application.purchase(\n", " PurchaseRequest(\n", " request_id=\"request-cookbook-001\",\n", " resource_url=resource_url,\n", " purpose=purpose,\n", " idempotency_key=\"purchase-cookbook-001\",\n", " ),\n", " approval=approval,\n", " )\n", " recorder.record(purchase)\n", " return AgentPurchaseEvidence(\n", " status=purchase.status,\n", " report=purchase.report,\n", " receipt_id=purchase.receipt.receipt_id,\n", " amount=purchase.receipt.amount,\n", " currency=purchase.receipt.currency,\n", " requires_human_approval=purchase.authorization.requires_human_approval,\n", " ).model_dump_json()\n", "\n", "\n", "agent = Agent(\n", " name=\"Supplier research agent\",\n", " model=model,\n", " model_settings=ModelSettings(reasoning=Reasoning(effort=\"low\"), store=False),\n", " output_type=SupplierResearchOutput,\n", " tools=[x402_fetch],\n", " instructions=(\n", " \"Call x402_fetch exactly once for the approved supplier report. \"\n", " \"Return only the typed summary supported by its receipt.\"\n", " ),\n", ")\n", "\n", "{\"agent\": agent.name, \"tools\": [tool.name for tool in agent.tools]}" ] }, { "cell_type": "markdown", "id": "x402-sequence", "metadata": {}, "source": [ "## 4. Running the x402 payment workflow\n", "\n", "x402 uses the HTTP `402 Payment Required` response to communicate the terms of a paid request. When the application first requests the supplier report, the merchant returns a challenge with the amount, asset, network, recipient, and expiry.\n", "\n", "The application checks those terms against its approved policy. If the merchant, price, request purpose, and human approval are valid, AgentCore Payments creates one bounded proof in the connected AWS workflow. The local example uses a synthetic proof to demonstrate the same sequence. The application retries the request with the proof, and the merchant returns the supplier report with HTTP `200`.\n", "\n", "![The x402 request sequence between the agent, application, merchant, and AgentCore Payments](../../../../images/partners/AWS/controlled-agentic-commerce-x402-sequence.png)\n", "\n", "*The merchant's 402 response supplies payment terms. Application approval occurs before a payment proof is requested.*\n", "\n", "`Runner.run` executes the Agents SDK loop. The scripted model requests `x402_fetch` on its first turn and prepares a typed supplier summary after the tool returns. The application checks that summary against the purchase it actually recorded.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "run-agent", "metadata": {}, "outputs": [], "source": [ "from agents import RunConfig, Runner\n", "\n", "from agentic_commerce.agent import (\n", " DEFAULT_PROMPT,\n", " SupplierResearchRun,\n", " validate_supplier_research_output,\n", ")\n", "\n", "result = await Runner.run(\n", " agent,\n", " DEFAULT_PROMPT,\n", " max_turns=4,\n", " run_config=RunConfig(\n", " tracing_disabled=True,\n", " workflow_name=\"Synthetic Agentic Commerce\",\n", " ),\n", ")\n", "purchase = validate_supplier_research_output(result.final_output, recorder)\n", "run = SupplierResearchRun(output=result.final_output, purchase=purchase)\n", "\n", "{\n", " \"model_turns\": model.model_turns,\n", " \"tool_calls\": model.requested_tools,\n", " \"merchant_requests\": merchant.request_count,\n", " \"synthetic_charges\": local_payments.charge_count,\n", " \"model_inference_calls\": 0,\n", " \"aws_calls\": 0,\n", " \"value_transferred\": False,\n", "}" ] }, { "cell_type": "markdown", "id": "responses-api-intro", "metadata": {}, "source": [ "### Using the Responses API directly\n", "\n", "The OpenAI Agents SDK coordinates the tool loop in the previous example, but the payment workflow does not require that orchestration layer. The Responses API can expose `x402_fetch` as a function, return the model's proposed arguments, and accept a `function_call_output` after the application handles the purchase.\n", "\n", "The application still checks the merchant, purpose, approval, and spending limit before making a payment. AgentCore Payments remains responsible for generating the bounded proof in the connected AWS workflow. The example below uses the same simulated merchant and payment processor as the local walkthrough.\n", "\n", "This alternative calls a real Bedrock-hosted GPT-5.6 Sol model, so it runs only when `ALLOW_PAID_INFERENCE=1` and valid AWS credentials are configured. It does not enable a live payment or transfer funds.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "run-responses-api", "metadata": {}, "outputs": [], "source": [ "import json\n", "import os\n", "\n", "from openai import OpenAI\n", "from openai.providers import bedrock\n", "\n", "responses_tool = {\n", " \"type\": \"function\",\n", " \"name\": \"x402_fetch\",\n", " \"description\": \"Fetch one approved supplier report.\",\n", " \"parameters\": {\n", " \"type\": \"object\",\n", " \"properties\": {\n", " \"resource_url\": {\"type\": \"string\"},\n", " \"purpose\": {\"type\": \"string\"},\n", " },\n", " \"required\": [\"resource_url\", \"purpose\"],\n", " \"additionalProperties\": False,\n", " },\n", " \"strict\": True,\n", "}\n", "responses_result = {\n", " \"result\": \"SKIPPED\",\n", " \"reason\": \"Set ALLOW_PAID_INFERENCE=1 to call the Bedrock model.\",\n", " \"model_calls\": 0,\n", " \"live_payment_calls\": 0,\n", "}\n", "\n", "if os.environ.get(\"ALLOW_PAID_INFERENCE\") == \"1\":\n", " responses_client = OpenAI(\n", " provider=bedrock(\n", " region=os.environ.get(\"AWS_REGION\", \"us-east-2\"),\n", " profile=os.environ.get(\"BEDROCK_AWS_PROFILE\") or None,\n", " api_key=None,\n", " )\n", " )\n", " prompt = (\n", " f\"Call x402_fetch for {RESOURCE_URL} with purpose \"\n", " \"supplier_due_diligence. After the tool returns, provide JSON \"\n", " \"containing supplier, receipt_id, amount, and currency.\"\n", " )\n", " first_response = responses_client.responses.create(\n", " model=\"openai.gpt-5.6-sol\",\n", " input=prompt,\n", " tools=[responses_tool],\n", " tool_choice={\"type\": \"function\", \"name\": \"x402_fetch\"},\n", " parallel_tool_calls=False,\n", " reasoning={\"effort\": \"low\"},\n", " max_output_tokens=128,\n", " store=False,\n", " )\n", " requested_tools = [\n", " item for item in first_response.output if item.type == \"function_call\"\n", " ]\n", " if len(requested_tools) != 1 or requested_tools[0].name != \"x402_fetch\":\n", " raise AgentResultInvalid(\n", " \"access_count_invalid\", \"Expected exactly one x402_fetch request.\"\n", " )\n", "\n", " arguments = json.loads(requested_tools[0].arguments)\n", " responses_now = datetime.now(UTC)\n", " responses_application, responses_merchant, responses_payments = build_demo(\n", " responses_now\n", " )\n", " responses_request_id = \"request-responses-cookbook-001\"\n", " responses_approval = approval.model_copy(\n", " update={\n", " \"approval_id\": \"approval-responses-cookbook-001\",\n", " \"request_id\": responses_request_id,\n", " \"approved_at\": responses_now,\n", " \"expires_at\": responses_now + timedelta(minutes=10),\n", " }\n", " )\n", " responses_purchase = responses_application.purchase(\n", " PurchaseRequest(\n", " request_id=responses_request_id,\n", " resource_url=arguments[\"resource_url\"],\n", " purpose=arguments[\"purpose\"],\n", " idempotency_key=\"purchase-responses-cookbook-001\",\n", " ),\n", " approval=responses_approval,\n", " )\n", " responses_evidence = AgentPurchaseEvidence(\n", " status=responses_purchase.status,\n", " report=responses_purchase.report,\n", " receipt_id=responses_purchase.receipt.receipt_id,\n", " amount=responses_purchase.receipt.amount,\n", " currency=responses_purchase.receipt.currency,\n", " requires_human_approval=(\n", " responses_purchase.authorization.requires_human_approval\n", " ),\n", " )\n", " response_input = [\n", " {\"role\": \"user\", \"content\": prompt},\n", " *(\n", " item.model_dump(mode=\"json\", exclude_none=True)\n", " for item in first_response.output\n", " ),\n", " {\n", " \"type\": \"function_call_output\",\n", " \"call_id\": requested_tools[0].call_id,\n", " \"output\": responses_evidence.model_dump_json(),\n", " },\n", " ]\n", " final_response = responses_client.responses.create(\n", " model=\"openai.gpt-5.6-sol\",\n", " input=response_input,\n", " tools=[responses_tool],\n", " tool_choice=\"none\",\n", " parallel_tool_calls=False,\n", " reasoning={\"effort\": \"low\"},\n", " max_output_tokens=256,\n", " store=False,\n", " )\n", " final_output = json.loads(final_response.output_text)\n", " expected_output = {\n", " \"supplier\": responses_purchase.report.supplier,\n", " \"receipt_id\": responses_purchase.receipt.receipt_id,\n", " \"amount\": str(responses_purchase.receipt.amount),\n", " \"currency\": responses_purchase.receipt.currency,\n", " }\n", " if any(final_output.get(key) != value for key, value in expected_output.items()):\n", " raise AgentResultInvalid(\n", " \"agent_output_mismatch\", \"The model response did not match its receipt.\"\n", " )\n", " responses_result = {\n", " \"result\": \"PASSED\",\n", " \"responses_api_calls\": 2,\n", " \"tool_calls\": [requested_tools[0].name],\n", " \"merchant_requests\": responses_merchant.request_count,\n", " \"synthetic_charges\": responses_payments.charge_count,\n", " \"receipt_verified\": True,\n", " \"live_payment_calls\": 0,\n", " \"value_transferred\": False,\n", " }\n", "\n", "responses_result" ] }, { "cell_type": "markdown", "id": "inspect-intro", "metadata": {}, "source": [ "## 5. Verifying the supplier report, receipt, and audit history\n", "\n", "The model returns a structured supplier summary, but a valid schema only establishes that the response has the expected shape. The application still needs to check whether the reported receipt, amount, currency, and supplier details match the completed tool request.\n", "\n", "The first cell compares the agent's proposed result with the application-owned receipt. The second cell shows the audit sequence from the initial resource request through authorization, payment proof creation, the merchant retry, and returned content.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "inspect-output", "metadata": {}, "outputs": [], "source": [ "proposal = run.output.model_dump(mode=\"json\")\n", "receipt = run.purchase.receipt.model_dump(mode=\"json\")\n", "\n", "{\n", " \"typed_agent_proposal\": proposal,\n", " \"application_receipt\": {\n", " \"receipt_id\": receipt[\"receipt_id\"],\n", " \"amount\": receipt[\"amount\"],\n", " \"currency\": receipt[\"currency\"],\n", " \"network\": receipt[\"network\"],\n", " \"reused\": receipt[\"reused\"],\n", " },\n", "}" ] }, { "cell_type": "code", "execution_count": null, "id": "inspect-audit", "metadata": {}, "outputs": [], "source": [ "[\n", " {\n", " \"sequence\": event.sequence,\n", " \"event_type\": event.event_type.value,\n", " }\n", " for event in run.purchase.audit_events\n", "]" ] }, { "cell_type": "markdown", "id": "negative-intro", "metadata": {}, "source": [ "## 6. Checking rejected purchases and fabricated receipts\n", "\n", "A useful payment example should show what happens when its controls reject a request. The first check submits another supplier report request without the required human approval. The application can inspect the merchant's challenge, but it must reject the purchase before creating another payment proof or charge.\n", "\n", "The existing synthetic charge count should remain unchanged after the request is denied.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "deny-unapproved-purchase", "metadata": {}, "outputs": [], "source": [ "from agentic_commerce.errors import PolicyDenied\n", "\n", "blocked_request = PurchaseRequest(\n", " request_id=\"request-cookbook-002\",\n", " resource_url=RESOURCE_URL,\n", " purpose=\"supplier_due_diligence\",\n", " idempotency_key=\"purchase-cookbook-002\",\n", ")\n", "\n", "try:\n", " application.purchase(blocked_request, approval=None)\n", "except PolicyDenied as exc:\n", " blocked_result = {\n", " \"status\": \"DENIED\",\n", " \"reason\": exc.code,\n", " \"synthetic_charges\": local_payments.charge_count,\n", " }\n", "else:\n", " raise AssertionError(\"Unapproved purchase unexpectedly succeeded\")\n", "\n", "blocked_result" ] }, { "cell_type": "markdown", "id": "fabricated-receipt-intro", "metadata": {}, "source": [ "The second check changes the receipt identifier in an otherwise valid supplier summary. The output validator compares that response with the purchase observed by the application and rejects the mismatch.\n", "\n", "These two checks cover separate boundaries: authorization before payment and evidence verification after the model responds.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "negative-test", "metadata": {}, "outputs": [], "source": [ "from agentic_commerce.agent import (\n", " PurchaseResultRecorder,\n", " validate_supplier_research_output,\n", ")\n", "\n", "fabricated = run.output.model_copy(update={\"receipt_id\": \"receipt-fabricated\"})\n", "try:\n", " validate_supplier_research_output(\n", " fabricated,\n", " PurchaseResultRecorder(results=[run.purchase]),\n", " )\n", "except AgentResultInvalid as exc:\n", " negative_result = {\"status\": \"REJECTED\", \"code\": exc.code}\n", "else:\n", " raise AssertionError(\"Fabricated output unexpectedly passed validation\")\n", "\n", "negative_result" ] }, { "cell_type": "markdown", "id": "agentcore-option", "metadata": {}, "source": [ "## 7. Connecting AgentCore Payments to the AWS testnet\n", "\n", "The connected supplier research workflow uses a Bedrock-hosted OpenAI model, AgentCore Payments, and an approved x402 merchant on Base Sepolia. The application keeps the same merchant policy, spending approval, request identity, and receipt checks demonstrated in the local simulation. AgentCore Payments creates a short-lived session with its own maximum spending limit.\n", "\n", "![Local supplier research and an AWS testnet request share the same application approval and spending controls](../../../../images/partners/AWS/controlled-agentic-commerce-local-and-testnet.png)\n", "\n", "*The local simulation and AWS testnet workflow share the same application controls. AgentCore Payments provides bounded payment sessions in the connected environment.*\n", "\n", "Before enabling the AWS path, an operator provisions the payment manager, connector, instrument, and exact-wallet signing permission outside the notebook. Model inference, session administration, and payment execution use separate responsibilities. `README.md` describes the setup, supported configuration, below-budget denial check, and cleanup procedure.\n", "\n", "AgentCore Runtime is not used by this notebook. The notebook process remains the application runtime, and all live payment gates are disabled unless an operator explicitly enables them.\n" ] }, { "cell_type": "markdown", "id": "aws-no-call-check", "metadata": {}, "source": [ "### Checking testnet readiness before network calls\n", "\n", "The readiness check inspects local configuration without contacting AWS, the model, the merchant, or the wallet. It confirms that the required payment opt-ins are present, AWS roles are separated, the session budget is finite, and no payment session is recorded in the local state file.\n", "\n", "The check also rejects proxy settings that are incompatible with the merchant connection's DNS and TLS protections. With the default configuration, the expected result is `NOT_READY` because live payment gates are disabled.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "agentcore-readiness", "metadata": {}, "outputs": [], "source": [ "from agentic_commerce.agentcore_e2e import (\n", " readiness_report,\n", " run_managed_e2e,\n", ")\n", "\n", "agentcore_readiness = readiness_report()\n", "agentcore_readiness" ] }, { "cell_type": "markdown", "id": "aws-read-only-intro", "metadata": {}, "source": [ "### Inspecting the payment instrument and network\n", "\n", "When `ALLOW_AGENTCORE_READ_ONLY=1` is explicitly enabled, the application can inspect the configured payment instrument without creating a payment session. The instrument must be `ACTIVE`, use the `ETHEREUM` wallet network, and report the exact `BASE_SEPOLIA` and `USDC` balance scope.\n", "\n", "A missing gate returns `SKIPPED`. A network or asset mismatch returns `NOT_READY`. These checks do not expose a balance amount, generate a payment proof, contact the merchant, or transfer value.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "aws-read-only-check", "metadata": {}, "outputs": [], "source": [ "from agentic_commerce.agentcore_infrastructure import (\n", " inspect_payment_instrument,\n", ")\n", "\n", "agentcore_infrastructure = inspect_payment_instrument()\n", "agentcore_infrastructure" ] }, { "cell_type": "markdown", "id": "agentcore-run-warning", "metadata": {}, "source": [ "### Running one approved testnet transaction\n", "\n", "An operator can enable the managed testnet run only after checking the approved merchant, recipient, asset, amount, wallet, and short-lived session configuration. The workflow requires all five explicit gates: `RUN_AGENTCORE_E2E`, `ALLOW_AGENTCORE_SESSION_ADMIN`, `ALLOW_PAID_INFERENCE`, `ALLOW_AGENTCORE_TESTNET`, and `APPROVE_AGENTCORE_TESTNET_PURCHASE`.\n", "\n", "The combined execution path checks those gates again before starting model inference or payment activity. It creates one bounded session, sends the agent's request through the application policy, requests an AgentCore payment proof, and retries the approved merchant once.\n", "\n", "Session creation is serialized. If local session persistence fails, the application attempts compensating cleanup. A successful or failed managed run also attempts to delete the session in `finally`. If cleanup fails, the operator must follow the recovery procedure in `README.md` before starting another run.\n", "\n", "With the default notebook configuration, the cell returns `SKIPPED` and performs no live operation.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "agentcore-e2e-run", "metadata": {}, "outputs": [], "source": [ "import json\n", "\n", "agentcore_e2e_result = await run_managed_e2e()\n", "if agentcore_e2e_result[\"result\"] not in {\"PASSED\", \"SKIPPED\"}:\n", " print(json.dumps(agentcore_e2e_result, indent=2, sort_keys=True))\n", " raise RuntimeError(\n", " \"Managed AgentCore test did not complete safely; review the \"\n", " \"sanitized report before any rerun.\"\n", " )\n", "\n", "agentcore_e2e_result" ] }, { "cell_type": "markdown", "id": "aws-evidence-intro", "metadata": {}, "source": [ "### Interpreting payment and settlement evidence\n", "\n", "The managed result records whether the model completed its tool call, AgentCore Payments produced the payment proof, the merchant returned HTTP `200`, and the temporary session was deleted. The application hashes the original HTTP response bytes and keeps provider credentials, payment headers, and internal identifiers out of the report.\n", "\n", "Merchant acceptance establishes that the paid request completed. Independent settlement and transaction finality require separate evidence. The example therefore reports `settlement_verified=false`.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "aws-evidence-check", "metadata": {}, "outputs": [], "source": [ "if agentcore_e2e_result[\"result\"] == \"PASSED\":\n", " assert agentcore_e2e_result[\"model_run_completed\"] is True\n", " assert agentcore_e2e_result[\"agentcore_payment_path_completed\"] is True\n", " assert agentcore_e2e_result[\"merchant_paid_retry_completed\"] is True\n", " assert agentcore_e2e_result[\"status_code\"] == 200\n", " assert agentcore_e2e_result[\"payment_attempts\"] == 1\n", " assert agentcore_e2e_result[\"session_cleanup\"] == \"DELETED\"\n", " assert agentcore_e2e_result[\"identifiers_logged\"] is False\n", " assert agentcore_e2e_result[\"settlement_verified\"] is False\n", "\n", "{\n", " \"result\": agentcore_e2e_result[\"result\"],\n", " \"session_cleanup\": agentcore_e2e_result.get(\"session_cleanup\", \"NOT_CREATED\"),\n", " \"settlement_verified\": agentcore_e2e_result.get(\"settlement_verified\", False),\n", " \"interpretation\": (\n", " \"Combined testnet path completed; settlement still needs separate evidence.\"\n", " if agentcore_e2e_result[\"result\"] == \"PASSED\"\n", " else \"Live path was not enabled; no testnet transaction was attempted.\"\n", " ),\n", "}" ] }, { "cell_type": "markdown", "id": "cloudwatch-lifecycle-evidence", "metadata": {}, "source": [ "### Optional CloudWatch lifecycle evidence\n", "\n", "This public notebook does not include a CloudWatch screenshot. For a reviewed live run, configure AgentCore Payments log delivery before the run and use CloudWatch only as sanitized lifecycle evidence. Do not share account IDs, request IDs, wallet data, payment identifiers, or proof headers.\n", "\n", "Lifecycle events can help confirm session creation, payment processing, and session deletion. They do not independently establish the payment amount, blockchain settlement, transaction finality, or production readiness.\n" ] }, { "cell_type": "markdown", "id": "completion", "metadata": {}, "source": [ "## Applying the payment workflow to other services\n", "\n", "The same structure can support other paid data sources, including premium search, sanctions screening, market data, or supplier verification. Each integration should define its approved merchants, permitted purposes, maximum amount, approval requirements, and session duration before the agent can request a purchase.\n", "\n", "The model chooses when a paid tool might help complete the user's task. Application code determines whether that request is authorized, records the resulting transaction evidence, and checks the model's final response against the actual tool result. AgentCore Payments provides an additional spending boundary when the workflow is connected to an approved AWS testnet session.\n" ] }, { "cell_type": "markdown", "id": "references", "metadata": {}, "source": [ "## References\n", "\n", "- [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/)\n", "- [Use OpenAI models through Amazon Bedrock](https://developers.openai.com/api/docs/guides/amazon-bedrock)\n", "- [AWS announcement: Agents that transact](https://aws.amazon.com/blogs/machine-learning/agents-that-transact-introducing-amazon-bedrock-agentcore-payments-built-with-coinbase-and-stripe/)\n", "- [How AgentCore Payments works](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-how-it-works.html)\n", "- [AgentCore Payments prerequisites](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-prerequisites.html)\n", "- [Create bounded payment sessions](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-create-session.html)\n", "- [Separate payment IAM roles](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-iam-roles.html)\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.13" } }, "nbformat": 4, "nbformat_minor": 5 }