Paybond integrates with Google ADK at the FunctionTool execution boundary — Harbor verify runs before side-effecting tools execute, then auto-evidence fires after success. Model inference stays on Gemini and your ADK runner; Paybond guards paid and side-effecting tool calls only.
Install
Install
npm install @paybond/google-adk @google/adkImport from @paybond/google-adk
import { createPaybondGoogleAdkConfig } from "@paybond/google-adk";- Equivalent subpath on the core package: `@paybond/kit/google-adk` — use `@paybond/kit` when you need multiple adapters in one app.
- Python: `paybond agent demo google-adk smoke` requires the optional `google-adk` extra. Use `pip install "paybond-kit[google-adk]"`, `pipx install 'paybond-kit[google-adk]'`, or `pipx inject paybond-kit google-adk` (when base paybond-kit is already installed).
- Smoke: `paybond agent demo google-adk smoke --operation paid-tool --requested-spend-cents 100 --evidence-preset cost_and_completion --format json`.
Recommended wiring
One-liner (sandbox): paybond.instrument({ policy, framework: "google-adk", tools }) or paybond.agent({ policy, framework: "google-adk", tools }) returns guarded ADK tools. Production: omit sandbox and call instrumented.bind() per session.
TypeScript
import { FunctionTool, LlmAgent } from "@google/adk"; import { Paybond } from "@paybond/kit"; const paybond = await Paybond.open({ apiKey: process.env.PAYBOND_API_KEY! }); const bookHotelTool = new FunctionTool({ name: "travel.book_hotel", description: "Book a hotel room", parameters: { type: "object", properties: { city: { type: "string" }, estimatedPriceCents: { type: "integer", minimum: 0 }, }, required: ["city", "estimatedPriceCents"], }, execute: async (args) => bookHotel(args), }); const { agentTools: tools } = await paybond.instrument({ policy: "./paybond.policy.yaml", framework: "google-adk", tools: [bookHotelTool, searchWebTool], }); const agent = new LlmAgent({ name: "Travel", tools });
Python
from google.adk.tools import FunctionTool from google.adk.agents import LlmAgent from paybond_kit import Paybond paybond = await Paybond.open(api_key=os.environ["PAYBOND_API_KEY"]) def book_hotel(city: str, estimated_price_cents: int) -> dict: """Book a hotel room.""" ... book_hotel_tool = FunctionTool(book_hotel) result = await paybond.instrument( policy="./paybond.policy.yaml", framework="google-adk", tools=[book_hotel_tool, search_web_tool], ) agent = LlmAgent(name="Travel", tools=result.tools)
Already bound a run? Use createPaybondGoogleAdkConfig(run, tools) / create_paybond_google_adk_config(run, tools) — returns { tools, wrapTool } / { tools, wrap_tool } for incremental wiring. Prefer LlmAgent(tools=config.tools) with pre-wrapped tools.
See Agent middleware for run binding, registry rules, and tenant isolation.
Advanced / manual wiring
When you need step-by-step control over registry and bind:
- Bind a
PaybondAgentRunwith a tool registry (sandbox bootstrap or production attach). - Register side-effecting tools on the registry so spend resolvers read tool args (for example
amount_centsfrom tool kwargs). - Wrap tools with
createPaybondGoogleAdkConfig/create_paybond_google_adk_configbefore registering them onLlmAgent/ sub-agents.
TypeScript
import { FunctionTool } from "@google/adk"; import { createPaybondGoogleAdkConfig } from "@paybond/google-adk"; const submitPoTool = new FunctionTool({ name: "procurement.submit_po", description: "Submit a purchase order", parameters: { /* ... */ }, execute: async (args) => submitPo(args), }); const config = createPaybondGoogleAdkConfig(run, [submitPoTool]); const guardedTools = config.tools; // Or wrap incrementally: config.wrapTool(newTool)
Python
from google.adk.tools import FunctionTool from paybond_kit.google_adk import create_paybond_google_adk_config submit_po_tool = FunctionTool(submit_po) config = create_paybond_google_adk_config(run, [submit_po_tool]) guarded_tools = config.tools # Or wrap incrementally: config.wrap_tool(new_tool)
Non-side-effecting tools (per registry) pass through unchanged.
Scaffold and smoke
# TypeScript paybond init agent-middleware --framework google-adk --out paybond-google-adk.ts # Python paybond init agent-middleware --framework google-adk --out paybond_google_adk.py paybond policy init --preset saas --out paybond.policy.yaml
Validate authorization and evidence without a live ADK agent or LLM:
paybond login paybond agent demo google-adk smoke \ --operation paid-tool \ --requested-spend-cents 100 \ --evidence-preset cost_and_completion \ --format json
TypeScript: npm install @paybond/google-adk @google/adk. Python optional extra: pip install "paybond-kit[google-adk]". With pipx: pipx install 'paybond-kit[google-adk]' or pipx inject paybond-kit google-adk.
Approval holds
When Harbor returns an approval hold, surface it to operators, approve in the tenant console, then retry with the same operation, amount, metadata, and approvalToken. Hard denials must not execute the tool.
- Python: guarded tools raise
RuntimeErrorwith a descriptive Paybond message (Paybond capability approval required: …orPaybond capability denied: …) so the ADK agent loop surfaces the hold without performing the side effect. After quorum, store the console token keyed by ADKfunction_call_id, then retry:
run.store_approval_token(tool_call_id, approval_token_from_console)
Pass the same tool_call_id on retry (ADK reuses function_call_id on the tool context). Wrapped tools read it via run.get_approval_token(tool_call_id).
- TypeScript: guarded tools rethrow clear
Errormessages (Paybond capability approval required: …/Paybond capability denied: …) with the original Harbor error ascause. Use the ADKfunctionCallIdfromtoolContextwhen storing a retry token:
run.storeApprovalToken(toolCallId, approvalTokenFromConsole);
Pass the same toolCallId on retry (ADK reuses functionCallId on the tool context). Hard denials must not execute the tool.
A2A complementarity
Paybond publishes A2A discovery (GET /.well-known/agent-card.json, GET /protocol/v2/a2a/task-contracts) beside ADK's RemoteA2aAgent — use Paybond for card/contracts discovery and Harbor-backed spend on local tools. Each agent that executes paid tools must wrap FunctionTool locally with Paybond; there is no A2A spend proxy in v1. Remote hops through AgentTool / RemoteA2aAgent do not inherit Paybond guards from the caller.
Known limitations
Paybond only governs tools that go through the wrap helper:
- GOOGLE_SEARCH and ADK built-ins — provider-executed or ADK-managed tools are not intercepted unless you replace them with local
FunctionToolhandlers. - AgentTool / RemoteA2aAgent remote hops — spend controls apply only on the process that wraps and executes the tool; remote agents need their own Paybond bind.
- Unwrapped tools — tools added to
LlmAgentafter wrap (or without passingconfig.tools) bypass Harbor. - LongRunning tools —
isLongRunningtools are wrapped when registered, but Paybond does not model async human-in-the-loop completion beyond Harbor approval holds. before_tool_callbackis NOT the Paybond path — ADK callbacks can observe tool calls but do not substitute for Harbor verify, capability tokens, or auto-evidence on the wrappedexecuteboundary.
For raw Gemini SDK function calling without ADK, use Gemini with agent-agnostic.
Related
- Agent middleware — run binding, registry, auto-evidence
- Agent-agnostic adapter — fallback for custom orchestrators and raw Gemini SDK
- Gemini with agent-agnostic — Google AI SDK function calling without ADK
- Support matrix — shipped framework surfaces
- Google ADK spend controls guide — task-oriented wiring walkthrough