zambo-adk
Zambo verifiable execution receipts for Google ADK (Agent Development Kit) agents.
Attach receipt recording to any ADK agent and every tool execution
becomes a hash-chained step in a verifiable workflow receipt
(AER-1, Section 8): each step carries the tool name, arguments,
result, agent identity, and timestamps, with one Merkle root over the
whole run. The hooks only observe: every callback returns None, so
ADK executes tools exactly as it would without us. No network calls
until you call mint().
Works with LlmAgent and any agent carrying the standard ADK tool
callbacks, via per-agent callbacks or a runner-wide plugin.
Install
pip install zambo-adk
Requires google-adk>=1.0 (installed automatically).
Quickstart (zero API keys)
This runs a real ADK agent end to end with a scripted fake model, no keys, no network, and ends at a live receipt URL on zambo.dev:
import asyncio
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types as genai_types
from zambo_adk import AER1ADKCollector, attach_receipts
from zambo_adk.testing import scripted_agent
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
async def main():
collector = AER1ADKCollector(goal="Add 40 and 2")
agent = scripted_agent(
"calc", [add],
script=[("add", {"a": 40, "b": 2})],
final_text="42",
)
attach_receipts(agent, collector) # line 1: attach
session_service = InMemorySessionService()
session = await session_service.create_session(app_name="app", user_id="u1")
runner = Runner(agent=agent, app_name="app", session_service=session_service)
message = genai_types.Content(
parts=[genai_types.Part(text="Add 40 and 2")], role="user"
)
async for _event in runner.run_async(
user_id="u1", session_id=session.id, new_message=message
):
pass # line 2: run
receipt = collector.finalize(final_answer="42")
assert collector.verify(receipt) == [] # VALID
print(collector.mint(receipt)) # line 3: live receipt URL
asyncio.run(main())
Output:
recorded 1 tool call(s), receipt is VALID
live receipt: https://zambo.dev/run/6b3e89e9-1952-4850-aef4-a9534688637c
Swap scripted_agent for your own LlmAgent with a real model and
the same three lines give you receipts on every tool call.
Runner-wide plugin
To cover every agent under a Runner, including sub-agents, register the plugin on the App instead of attaching per-agent callbacks:
from google.adk.apps import App
from zambo_adk import AER1ADKCollector, AER1ADKPlugin
collector = AER1ADKCollector(goal="Research the launch plan")
plugin = AER1ADKPlugin(collector)
app = App(name="my_app", root_agent=agent, plugins=[plugin])
Plugins run before agent-level callbacks, so the plugin also sees tool calls that an agent callback short-circuits.
What the receipt contains
Workflow level (AER-1 Section 8, Table 2):
type,version,workflow_id,receipt_id,session_idgoal,statussteps: one record per tool execution, seq 1..n in ordermerkle_root: Section 8.1 root over the ordered step receipt idsoutput_hash: SHA-256 of the final answerverify_url: where the verification procedure is documented
Step level (AER-1 Section 8, Table 3), plus agent identity:
seq,receipt_id,tool,receipt_hash,started_at,ended_at,statusagent: the name of the ADK agent that acted (every step)
Each step receipt_hash is SHA-256 over the canonical JSON of the
tool name, arguments, result, and error flag. The hash commits to the
content; the receipt stays compact. Tool errors are recorded as steps
with status: "error" and the exception captured, so failures are
auditable too.
Verification
collector.verify(receipt) runs the full offline check and returns a
list of failure reasons, empty when valid:
- all Table 2 / Table 3 members present and well-formed
seqvalues exactly 1..n in order, no gaps- no two steps share a
receipt_id merkle_rootmatches the recomputed Section 8.1 root- strict RFC 3339 timestamps, lowercase UUIDs, 64-char hex digests
Tamper with any field and verification fails. Try it:
receipt["steps"][0]["tool"] = ""
assert collector.verify(receipt) != [] # fails, as it should
Minting a live Zambo receipt
collector.mint(receipt) submits the finalized AER-1 receipt to
zambo.dev for verification and returns the live audit URL
(https://zambo.dev/run/<receipt-id>). It uses the free zambo.dev
MCP tier: no auth, no API keys. The mint itself is a Zambo tool call,
so it carries its own verifiable Zambo receipt.
Notes
attach_receiptspreserves callbacks already on the agent: ours are appended to the existing callback lists.- The collector never touches the network and never changes what the
agents do until
mint()is called. A recording failure can never break a run. session_iddefaults to a fresh UUID per collector; pass your own to correlate receipts across runs.ScriptedModel/scripted_agentinzambo_adk.testingare test helpers: scripted tool-call sequences with no model credentials.- Verified against
google-adk2.11.0.
Spec
AER-1: Agent Execution Receipts, draft-zambo-aer1
(an IETF draft):
https://datatracker.ietf.org/doc/draft-zambo-aer1/
License
Apache-2.0
Metadata
Release files for zambo-adk 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| zambo_adk-0.1.1.tar.gz | 15.1 kB | Without provenance | Details for zambo_adk-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| zambo_adk-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for zambo_adk-0.1.1-py3-none-any.whl |
Total release size: 28.3 kB
Release files / zambo_adk-0.1.1.tar.gz
| Download URL | zambo_adk-0.1.1.tar.gz |
|---|---|
| Size | 15.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e7ca03a1f0c9fe905a36afd937da495fc15fea52a6fc38376a850a2297f9f945
|
|
BLAKE2b-256 checksum How to use checksums |
21ef8e076f4df75f4dd40b92cd01638438ba7009a16544c3cf87bd5369ae5811
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
rambo-pypi-grab/0.1.0
|
Release files / zambo_adk-0.1.1-py3-none-any.whl
| Download URL | zambo_adk-0.1.1-py3-none-any.whl |
|---|---|
| Size | 13.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
482ffb847f982951785b98de8f467b7b4313beb6be5726fec734db3cadcb3f93
|
|
BLAKE2b-256 checksum How to use checksums |
99ee05b42c8d07d81bd50a145a6f0ebfe18f7d381d4a20757c8d7e669f1e701f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
rambo-pypi-grab/0.1.0
|
Security for zambo-adk 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.