Introduction
Here’s a fifth, production-grade agent pattern for you to implement: an Invoice Approval & Payment Agent. This agent reads submitted invoices, checks compliance with payment policies, validates approvals, and either authorizes payment or requests additional approvals. It operates on the principle of least privilege—never authorizing payments without approval from the appropriate individuals or teams, and never claiming success without executing the payment action and returning a receipt.
The Use Case
An invoice is submitted by a vendor (containing invoice number, amount, due date, and other details). The agent must ensure the invoice follows payment policies (e.g., within budget, approved by the right person), confirm that the correct payment method is used, and—if everything checks out—approve the payment or request further actions like approvals or payment corrections. All actions should be tracked with receipts, and the decision-making process should be auditable.
Prompt Contract (the agent’s interface)
# file: contracts/invoice_approval_v1.yaml
role: "InvoiceApprovalAgent"
scope: >
Verify and approve invoices based on payment policies. Ask once if critical fields are missing (invoice_id, amount, vendor, due_date).
Propose tool calls for compliance checks, approvals, and payments; never assert success without a receipt.
output:
type: object
required: [summary, decision, invoice_details, citations, next_steps, tool_proposals]
properties:
summary: {type: string, maxWords: 80}
decision: {type: string, enum: ["approve","reject","need_approval","need_more_info"]}
invoice_details:
type: object
required: [invoice_id, vendor, amount_cents, due_date, payment_method]
properties:
invoice_id: {type: string}
vendor: {type: string}
amount_cents: {type: integer}
due_date: {type: string}
payment_method: {type: string, enum: ["bank_transfer", "check", "credit_card"]}
citations: {type: array, items: {type: string}}
next_steps: {type: array, items: {type: string}, maxItems: 6}
tool_proposals:
type: array
items:
type: object
required: [name, args, preconditions, idempotency_key]
properties:
name: {type: string, enum: [CheckInvoicePolicy, CheckApproval, AuthorizePayment, RequestApproval]}
args: {type: object}
preconditions: {type: string}
idempotency_key: {type: string}
policy_id: "payment_policy.v5.us"
citation_rule: "1–2 minimal-span claim_ids per factual sentence"
decoding:
narrative: {top_p: 0.92, temperature: 0.72, stop: ["\n\n## "]}
bullets: {top_p: 0.82, temperature: 0.45}
Example claims (context given to the model)
[
{"claim_id":"policy:invoice:payment_cap","text":"Invoices exceeding $10,000 must be approved by the CFO.",
"effective_date":"2025-06-01","source_id":"doc:payment_policy_v5","span":"$10,000 must be approved by the CFO"},
{"claim_id":"policy:invoice:payment_method","text":"Only bank transfers or checks are allowed for amounts over $5,000.",
"effective_date":"2025-06-01","source_id":"doc:payment_policy_v5","span":"bank transfers or checks"},
{"claim_id":"policy:vendor:approval_required","text":"Vendor X requires manager approval for invoices exceeding $1,000.",
"effective_date":"2025-06-01","source_id":"doc:payment_policy_v5","span":"Vendor X requires manager approval"},
{"claim_id":"vendor:acme_inc","text":"Vendor ACME Inc has payment terms of Net 30.",
"effective_date":"2025-06-01","source_id":"vendor_data","span":"Net 30"}
]
Tool Interfaces (typed, with receipts)
# tools.py
from pydantic import BaseModel
from typing import Optional, Dict
from datetime import date
class CheckInvoicePolicyArgs(BaseModel):
invoice_id: str
amount_cents: int
vendor: str
payment_method: str
due_date: date
class CheckApprovalArgs(BaseModel):
invoice_id: str
amount_cents: int
vendor: str
approver_id: str
class AuthorizePaymentArgs(BaseModel):
invoice_id: str
amount_cents: int
payment_method: str
due_date: date
class RequestApprovalArgs(BaseModel):
invoice_id: str
amount_cents: int
vendor: str
approver_id: str
class ToolReceipt(BaseModel):
tool: str
ok: bool
ref: str
message: str = ""
data: Optional[Dict] = None
# adapters.py (demo logic)
from tools import *
from datetime import date, timedelta
VENDORS = {"Acme Inc": {"max_approval": 1000, "payment_method": "bank_transfer", "manager_id": "M001"}}
APPROVALS = {("Acme Inc", 1500): "appr-approve"}
def check_invoice_policy(a: CheckInvoicePolicyArgs) -> ToolReceipt:
if a.amount_cents > 10000 and a.payment_method != "bank_transfer" and a.payment_method != "check":
return ToolReceipt(tool="CheckInvoicePolicy", ok=False, ref="policy-invalid-method",
message="Invalid payment method for invoice over $10,000")
if a.amount_cents > VENDORS[a.vendor]["max_approval"]:
return ToolReceipt(tool="CheckInvoicePolicy", ok=False, ref="policy-approval-required",
message="Requires approval for invoices over $1,000")
return ToolReceipt(tool="CheckInvoicePolicy", ok=True, ref="policy-ok", message="Invoice meets policy")
def check_approval(a: CheckApprovalArgs) -> ToolReceipt:
approval = APPROVALS.get((a.vendor, a.amount_cents))
if approval:
return ToolReceipt(tool="CheckApproval", ok=True, ref=approval, message="Approval found")
return ToolReceipt(tool="CheckApproval", ok=False, ref="no-approval", message="Approval required")
def authorize_payment(a: AuthorizePaymentArgs) -> ToolReceipt:
return ToolReceipt(tool="AuthorizePayment", ok=True, ref=f"payment-{a.invoice_id}",
message="Payment authorized", data={"amount_cents": a.amount_cents})
def request_approval(a: RequestApprovalArgs) -> ToolReceipt:
return ToolReceipt(tool="RequestApproval", ok=True, ref=f"approval-{a.invoice_id}",
message="Approval requested", data={"approver_id": a.approver_id})

Join the conversation! Your thoughts help the community grow.