Banking Agent Harness
A banking simulation that puts policy checks, a separate confirmation turn, and execution verification between an AI agent and a mock ledger.
Problem & context
Explore how a conversational banking agent can propose transfers while deterministic Python tools control validation, explicit confirmation, and simulated transaction outcomes.
My contribution
ADK integration, deterministic transfer policy and confirmation state, mock transaction execution, usage budgets, and redacted audit events.
- Initiate synthetic transfer{"account_last4": "5678", "amount": 1000, "beneficiary_name": "Aaron Johnson", "status": "PENDING_CONFIRMATION"}
- Later user turn: confirm{"amount": 1000, "beneficiary_name": "Aaron Johnson", "new_balance": 199000.0, "status": "SUCCESS", "transaction_id": "TXN001"}
- Repeat confirmation{"reason": "There is no pending transfer to confirm.", "status": "NO_PENDING_TRANSFER"}
Mock transaction succeeds once; balance changes from 200000 to 199000. This is an in-memory simulation, with no bank connection.
How this was checked
Executed unchanged deterministic banking tool/service source with synthetic in-memory accounts and mocked SUCCESS executor setting. ADK/provider startup and .env loading were omitted; ToolContext is a lightweight fixture and approval lifetime is the code default of 24 hours. This verifies these function paths only, not model behavior, live banking, production concurrency, or cross-process state.
Inspect recorded output ↗Inspect the workflow.
Local deterministic harness output with synthetic accounts. No real funds, bank integration, or provider call.
Failure path: The agent attempts confirmation during the original invocation, without a matching staged session proposal, or after deterministic validation rejects the request.
The tool returns an explicit confirmation-required, no-pending-transfer, or rejected status. The executed rejection and confirmation checks leave the mock balance unchanged; policy is revalidated again before execution.
An engineering decision.
confirm_transfer reads the actual staged record, invocation identifier, and user text, then revalidates current policy. The execution gate can be inspected and tested independently of model responses.
Implemented approach
Stage a validated transfer in session state and require an allowlisted confirmation in a later invocation before executing it.
Alternative & tradeoff
Rely on a system-prompt instruction or a model-supplied skip-confirmation flag.
Follow the system.
Verified behavior & limitations.
What the evidence establishes
- Executed locally: initiating a valid synthetic request leaves the mock balance unchanged; explicit confirmation in a later invocation produces SUCCESS and updates the mock balance from 200000 to 199000.
- Executed locally: repeat confirmation returns NO_PENDING_TRANSFER; confirmation within the initiating invocation returns CONFIRMATION_REQUIRED.
- Executed locally: a blocked mock beneficiary returns REJECTED with unchanged balance, and a separate session cannot confirm the first session's proposal.
- Source review: amount validation checks finite positive values and decimal precision, confirmation rechecks current balance/policy, and SUCCESS/PENDING/FAILED outcomes come from a separate mock executor.
Boundaries & remaining work
- This is an in-memory banking simulation. No real accounts, banking API, payment rail, or durable transaction ledger are connected.
- The executed scenarios call real deterministic functions with a synthetic ToolContext fixture. They do not verify an LLM conversation, ADK browser flow, Upstash quota integration, or live provider availability.
- Pending approvals use session state, while mock accounts and transactions are process-local. Cross-process persistence and production concurrency are outside the verified behavior.
Inspect the source.
References are pinned to the reviewed revision. Public sample screens demonstrate the interface with fixtures; source review and executed checks are labelled separately.