Skip to main content

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_id
  • goal, status
  • steps: one record per tool execution, seq 1..n in order
  • merkle_root: Section 8.1 root over the ordered step receipt ids
  • output_hash: SHA-256 of the final answer
  • verify_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, status
  • agent: 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
  • seq values exactly 1..n in order, no gaps
  • no two steps share a receipt_id
  • merkle_root matches 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_receipts preserves 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_id defaults to a fresh UUID per collector; pass your own to correlate receipts across runs.
  • ScriptedModel / scripted_agent in zambo_adk.testing are test helpers: scripted tool-call sequences with no model credentials.
  • Verified against google-adk 2.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)

Source distribution for zambo-adk 0.1.1
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)

Table of built distributions (wheels) for zambo-adk 0.1.1
File Interpreter ABI Platform Actions
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.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page