zambo-adk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.4
2
+ Name: zambo-adk
3
+ Version: 0.1.0
4
+ Summary: Zambo verifiable execution receipts for Google ADK agents
5
+ Author: Brennan Zambo
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://zambo.dev
8
+ Keywords: zambo,aer-1,google-adk,ai-agents,verifiable-receipts,audit,tracing
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: google-adk>=1.0
16
+
17
+ # zambo-adk
18
+
19
+ Zambo verifiable execution receipts for [Google ADK](https://google.github.io/adk-docs/) (Agent Development Kit) agents.
20
+
21
+ Attach receipt recording to any ADK agent and every tool execution
22
+ becomes a hash-chained step in a **verifiable workflow receipt**
23
+ (AER-1, Section 8): each step carries the tool name, arguments,
24
+ result, agent identity, and timestamps, with one Merkle root over the
25
+ whole run. The hooks only observe: every callback returns `None`, so
26
+ ADK executes tools exactly as it would without us. No network calls
27
+ until you call `mint()`.
28
+
29
+ Works with `LlmAgent` and any agent carrying the standard ADK tool
30
+ callbacks, via per-agent callbacks or a runner-wide plugin.
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ pip install zambo-adk
36
+ ```
37
+
38
+ Requires `google-adk>=1.0` (installed automatically).
39
+
40
+ ## Quickstart (zero API keys)
41
+
42
+ This runs a real ADK agent end to end with a scripted fake model, no
43
+ keys, no network, and ends at a live receipt URL on zambo.dev:
44
+
45
+ ```python
46
+ import asyncio
47
+ from google.adk.runners import Runner
48
+ from google.adk.sessions import InMemorySessionService
49
+ from google.genai import types as genai_types
50
+
51
+ from zambo_adk import AER1ADKCollector, attach_receipts
52
+ from zambo_adk.testing import scripted_agent
53
+
54
+
55
+ def add(a: int, b: int) -> int:
56
+ """Add two numbers."""
57
+ return a + b
58
+
59
+
60
+ async def main():
61
+ collector = AER1ADKCollector(goal="Add 40 and 2")
62
+
63
+ agent = scripted_agent(
64
+ "calc", [add],
65
+ script=[("add", {"a": 40, "b": 2})],
66
+ final_text="42",
67
+ )
68
+ attach_receipts(agent, collector) # line 1: attach
69
+
70
+ session_service = InMemorySessionService()
71
+ session = await session_service.create_session(app_name="app", user_id="u1")
72
+ runner = Runner(agent=agent, app_name="app", session_service=session_service)
73
+ message = genai_types.Content(
74
+ parts=[genai_types.Part(text="Add 40 and 2")], role="user"
75
+ )
76
+ async for _event in runner.run_async(
77
+ user_id="u1", session_id=session.id, new_message=message
78
+ ):
79
+ pass # line 2: run
80
+
81
+ receipt = collector.finalize(final_answer="42")
82
+ assert collector.verify(receipt) == [] # VALID
83
+ print(collector.mint(receipt)) # line 3: live receipt URL
84
+
85
+ asyncio.run(main())
86
+ ```
87
+
88
+ Output:
89
+
90
+ ```
91
+ recorded 1 tool call(s), receipt is VALID
92
+ live receipt: https://zambo.dev/run/6b3e89e9-1952-4850-aef4-a9534688637c
93
+ ```
94
+
95
+ Swap `scripted_agent` for your own `LlmAgent` with a real model and
96
+ the same three lines give you receipts on every tool call.
97
+
98
+ ## Runner-wide plugin
99
+
100
+ To cover every agent under a Runner, including sub-agents, register
101
+ the plugin on the App instead of attaching per-agent callbacks:
102
+
103
+ ```python
104
+ from google.adk.apps import App
105
+ from zambo_adk import AER1ADKCollector, AER1ADKPlugin
106
+
107
+ collector = AER1ADKCollector(goal="Research the launch plan")
108
+ plugin = AER1ADKPlugin(collector)
109
+ app = App(name="my_app", root_agent=agent, plugins=[plugin])
110
+ ```
111
+
112
+ Plugins run before agent-level callbacks, so the plugin also sees
113
+ tool calls that an agent callback short-circuits.
114
+
115
+ ## What the receipt contains
116
+
117
+ Workflow level (AER-1 Section 8, Table 2):
118
+
119
+ - `type`, `version`, `workflow_id`, `receipt_id`, `session_id`
120
+ - `goal`, `status`
121
+ - `steps`: one record per tool execution, seq 1..n in order
122
+ - `merkle_root`: Section 8.1 root over the ordered step receipt ids
123
+ - `output_hash`: SHA-256 of the final answer
124
+ - `verify_url`: where the verification procedure is documented
125
+
126
+ Step level (AER-1 Section 8, Table 3), plus agent identity:
127
+
128
+ - `seq`, `receipt_id`, `tool`, `receipt_hash`, `started_at`, `ended_at`, `status`
129
+ - `agent`: the name of the ADK agent that acted (every step)
130
+
131
+ Each step `receipt_hash` is SHA-256 over the canonical JSON of the
132
+ tool name, arguments, result, and error flag. The hash commits to the
133
+ content; the receipt stays compact. Tool errors are recorded as steps
134
+ with `status: "error"` and the exception captured, so failures are
135
+ auditable too.
136
+
137
+ ## Verification
138
+
139
+ `collector.verify(receipt)` runs the full offline check and returns a
140
+ list of failure reasons, empty when valid:
141
+
142
+ - all Table 2 / Table 3 members present and well-formed
143
+ - `seq` values exactly 1..n in order, no gaps
144
+ - no two steps share a `receipt_id`
145
+ - `merkle_root` matches the recomputed Section 8.1 root
146
+ - strict RFC 3339 timestamps, lowercase UUIDs, 64-char hex digests
147
+
148
+ Tamper with any field and verification fails. Try it:
149
+
150
+ ```python
151
+ receipt["steps"][0]["tool"] = ""
152
+ assert collector.verify(receipt) != [] # fails, as it should
153
+ ```
154
+
155
+ ## Minting a live Zambo receipt
156
+
157
+ `collector.mint(receipt)` submits the finalized AER-1 receipt to
158
+ zambo.dev for verification and returns the live audit URL
159
+ (`https://zambo.dev/run/<receipt-id>`). It uses the free zambo.dev
160
+ MCP tier: no auth, no API keys. The mint itself is a Zambo tool call,
161
+ so it carries its own verifiable Zambo receipt.
162
+
163
+ ## Notes
164
+
165
+ - `attach_receipts` preserves callbacks already on the agent: ours are
166
+ appended to the existing callback lists.
167
+ - The collector never touches the network and never changes what the
168
+ agents do until `mint()` is called. A recording failure can never
169
+ break a run.
170
+ - `session_id` defaults to a fresh UUID per collector; pass your own to
171
+ correlate receipts across runs.
172
+ - `ScriptedModel` / `scripted_agent` in `zambo_adk.testing` are test
173
+ helpers: scripted tool-call sequences with no model credentials.
174
+ - Verified against `google-adk` 2.11.0.
175
+
176
+ ## Spec
177
+
178
+ AER-1: Agent Execution Receipts, `draft-zambo-aer1`
179
+ (an IETF draft):
180
+ https://datatracker.ietf.org/doc/draft-zambo-aer1/
181
+
182
+ ## License
183
+
184
+ Apache-2.0
@@ -0,0 +1,168 @@
1
+ # zambo-adk
2
+
3
+ Zambo verifiable execution receipts for [Google ADK](https://google.github.io/adk-docs/) (Agent Development Kit) agents.
4
+
5
+ Attach receipt recording to any ADK agent and every tool execution
6
+ becomes a hash-chained step in a **verifiable workflow receipt**
7
+ (AER-1, Section 8): each step carries the tool name, arguments,
8
+ result, agent identity, and timestamps, with one Merkle root over the
9
+ whole run. The hooks only observe: every callback returns `None`, so
10
+ ADK executes tools exactly as it would without us. No network calls
11
+ until you call `mint()`.
12
+
13
+ Works with `LlmAgent` and any agent carrying the standard ADK tool
14
+ callbacks, via per-agent callbacks or a runner-wide plugin.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install zambo-adk
20
+ ```
21
+
22
+ Requires `google-adk>=1.0` (installed automatically).
23
+
24
+ ## Quickstart (zero API keys)
25
+
26
+ This runs a real ADK agent end to end with a scripted fake model, no
27
+ keys, no network, and ends at a live receipt URL on zambo.dev:
28
+
29
+ ```python
30
+ import asyncio
31
+ from google.adk.runners import Runner
32
+ from google.adk.sessions import InMemorySessionService
33
+ from google.genai import types as genai_types
34
+
35
+ from zambo_adk import AER1ADKCollector, attach_receipts
36
+ from zambo_adk.testing import scripted_agent
37
+
38
+
39
+ def add(a: int, b: int) -> int:
40
+ """Add two numbers."""
41
+ return a + b
42
+
43
+
44
+ async def main():
45
+ collector = AER1ADKCollector(goal="Add 40 and 2")
46
+
47
+ agent = scripted_agent(
48
+ "calc", [add],
49
+ script=[("add", {"a": 40, "b": 2})],
50
+ final_text="42",
51
+ )
52
+ attach_receipts(agent, collector) # line 1: attach
53
+
54
+ session_service = InMemorySessionService()
55
+ session = await session_service.create_session(app_name="app", user_id="u1")
56
+ runner = Runner(agent=agent, app_name="app", session_service=session_service)
57
+ message = genai_types.Content(
58
+ parts=[genai_types.Part(text="Add 40 and 2")], role="user"
59
+ )
60
+ async for _event in runner.run_async(
61
+ user_id="u1", session_id=session.id, new_message=message
62
+ ):
63
+ pass # line 2: run
64
+
65
+ receipt = collector.finalize(final_answer="42")
66
+ assert collector.verify(receipt) == [] # VALID
67
+ print(collector.mint(receipt)) # line 3: live receipt URL
68
+
69
+ asyncio.run(main())
70
+ ```
71
+
72
+ Output:
73
+
74
+ ```
75
+ recorded 1 tool call(s), receipt is VALID
76
+ live receipt: https://zambo.dev/run/6b3e89e9-1952-4850-aef4-a9534688637c
77
+ ```
78
+
79
+ Swap `scripted_agent` for your own `LlmAgent` with a real model and
80
+ the same three lines give you receipts on every tool call.
81
+
82
+ ## Runner-wide plugin
83
+
84
+ To cover every agent under a Runner, including sub-agents, register
85
+ the plugin on the App instead of attaching per-agent callbacks:
86
+
87
+ ```python
88
+ from google.adk.apps import App
89
+ from zambo_adk import AER1ADKCollector, AER1ADKPlugin
90
+
91
+ collector = AER1ADKCollector(goal="Research the launch plan")
92
+ plugin = AER1ADKPlugin(collector)
93
+ app = App(name="my_app", root_agent=agent, plugins=[plugin])
94
+ ```
95
+
96
+ Plugins run before agent-level callbacks, so the plugin also sees
97
+ tool calls that an agent callback short-circuits.
98
+
99
+ ## What the receipt contains
100
+
101
+ Workflow level (AER-1 Section 8, Table 2):
102
+
103
+ - `type`, `version`, `workflow_id`, `receipt_id`, `session_id`
104
+ - `goal`, `status`
105
+ - `steps`: one record per tool execution, seq 1..n in order
106
+ - `merkle_root`: Section 8.1 root over the ordered step receipt ids
107
+ - `output_hash`: SHA-256 of the final answer
108
+ - `verify_url`: where the verification procedure is documented
109
+
110
+ Step level (AER-1 Section 8, Table 3), plus agent identity:
111
+
112
+ - `seq`, `receipt_id`, `tool`, `receipt_hash`, `started_at`, `ended_at`, `status`
113
+ - `agent`: the name of the ADK agent that acted (every step)
114
+
115
+ Each step `receipt_hash` is SHA-256 over the canonical JSON of the
116
+ tool name, arguments, result, and error flag. The hash commits to the
117
+ content; the receipt stays compact. Tool errors are recorded as steps
118
+ with `status: "error"` and the exception captured, so failures are
119
+ auditable too.
120
+
121
+ ## Verification
122
+
123
+ `collector.verify(receipt)` runs the full offline check and returns a
124
+ list of failure reasons, empty when valid:
125
+
126
+ - all Table 2 / Table 3 members present and well-formed
127
+ - `seq` values exactly 1..n in order, no gaps
128
+ - no two steps share a `receipt_id`
129
+ - `merkle_root` matches the recomputed Section 8.1 root
130
+ - strict RFC 3339 timestamps, lowercase UUIDs, 64-char hex digests
131
+
132
+ Tamper with any field and verification fails. Try it:
133
+
134
+ ```python
135
+ receipt["steps"][0]["tool"] = ""
136
+ assert collector.verify(receipt) != [] # fails, as it should
137
+ ```
138
+
139
+ ## Minting a live Zambo receipt
140
+
141
+ `collector.mint(receipt)` submits the finalized AER-1 receipt to
142
+ zambo.dev for verification and returns the live audit URL
143
+ (`https://zambo.dev/run/<receipt-id>`). It uses the free zambo.dev
144
+ MCP tier: no auth, no API keys. The mint itself is a Zambo tool call,
145
+ so it carries its own verifiable Zambo receipt.
146
+
147
+ ## Notes
148
+
149
+ - `attach_receipts` preserves callbacks already on the agent: ours are
150
+ appended to the existing callback lists.
151
+ - The collector never touches the network and never changes what the
152
+ agents do until `mint()` is called. A recording failure can never
153
+ break a run.
154
+ - `session_id` defaults to a fresh UUID per collector; pass your own to
155
+ correlate receipts across runs.
156
+ - `ScriptedModel` / `scripted_agent` in `zambo_adk.testing` are test
157
+ helpers: scripted tool-call sequences with no model credentials.
158
+ - Verified against `google-adk` 2.11.0.
159
+
160
+ ## Spec
161
+
162
+ AER-1: Agent Execution Receipts, `draft-zambo-aer1`
163
+ (an IETF draft):
164
+ https://datatracker.ietf.org/doc/draft-zambo-aer1/
165
+
166
+ ## License
167
+
168
+ Apache-2.0
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "zambo-adk"
7
+ version = "0.1.0"
8
+ description = "Zambo verifiable execution receipts for Google ADK agents"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "Apache-2.0" }
12
+ authors = [{ name = "Brennan Zambo" }]
13
+ dependencies = ["google-adk>=1.0"]
14
+ keywords = ["zambo", "aer-1", "google-adk", "ai-agents", "verifiable-receipts", "audit", "tracing"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Software Development :: Libraries :: Python Modules",
20
+ ]
21
+
22
+ [project.urls]
23
+ Homepage = "https://zambo.dev"
24
+
25
+ [tool.setuptools.packages.find]
26
+ where = ["."]
27
+ include = ["zambo_adk*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,173 @@
1
+ """Tests for zambo-adk. No google-adk and no network required, except
2
+ test_mint which hits the live zambo.dev MCP (free tier, no auth)."""
3
+
4
+ import json
5
+
6
+ import pytest
7
+
8
+ from zambo_adk import AER1ADKCollector, attach_receipts
9
+ from zambo_adk.collector import (
10
+ _mint_audit_url,
11
+ merkle_root,
12
+ verify_workflow_receipt,
13
+ )
14
+ from zambo_adk.hooks import _CallbackState
15
+
16
+
17
+ class FakeTool:
18
+ def __init__(self, name):
19
+ self.name = name
20
+
21
+
22
+ class FakeContext:
23
+ def __init__(self, agent_name="test_agent"):
24
+ self.agent_name = agent_name
25
+
26
+
27
+ class FakeAgent:
28
+ def __init__(self):
29
+ self.before_tool_callback = None
30
+ self.after_tool_callback = None
31
+ self.on_tool_error_callback = None
32
+
33
+
34
+ def run_tool_through_callbacks(agent, tool_name, args, result):
35
+ tool = FakeTool(tool_name)
36
+ ctx = FakeContext()
37
+ for cb in agent.before_tool_callback:
38
+ assert cb(tool=tool, args=args, tool_context=ctx) is None
39
+ for cb in agent.after_tool_callback:
40
+ assert (
41
+ cb(tool=tool, args=args, tool_context=ctx, tool_response=result)
42
+ is None
43
+ )
44
+
45
+
46
+ def test_tool_call_recorded_as_step():
47
+ collector = AER1ADKCollector(goal="add numbers")
48
+ agent = attach_receipts(FakeAgent(), collector)
49
+ run_tool_through_callbacks(
50
+ agent, "add", {"a": 2, "b": 3}, {"result": 5}
51
+ )
52
+ assert collector.step_count == 1
53
+ receipt = collector.finalize(final_answer="5")
54
+ assert collector.verify(receipt) == []
55
+ step = receipt["steps"][0]
56
+ assert step["tool"] == "add"
57
+ assert step["status"] == "ok"
58
+ assert step["agent"] == "test_agent"
59
+
60
+
61
+ def test_error_tool_call_recorded_as_error():
62
+ collector = AER1ADKCollector(goal="risky run")
63
+ agent = attach_receipts(FakeAgent(), collector)
64
+ tool = FakeTool("divide")
65
+ ctx = FakeContext()
66
+ for cb in agent.before_tool_callback:
67
+ cb(tool=tool, args={"a": 1, "b": 0}, tool_context=ctx)
68
+ boom = ZeroDivisionError("division by zero")
69
+ for cb in agent.on_tool_error_callback:
70
+ assert (
71
+ cb(tool=tool, args={}, tool_context=ctx, exception=boom) is None
72
+ )
73
+ receipt = collector.finalize()
74
+ assert receipt["status"] == "error"
75
+ assert receipt["steps"][0]["status"] == "error"
76
+ assert collector.verify(receipt) == []
77
+
78
+
79
+ def test_existing_callbacks_preserved():
80
+ seen = []
81
+
82
+ def existing_before(tool=None, args=None, tool_context=None):
83
+ seen.append(("before", getattr(tool, "name", None)))
84
+ return None
85
+
86
+ collector = AER1ADKCollector(goal="preserve")
87
+ agent = FakeAgent()
88
+ agent.before_tool_callback = existing_before
89
+ attach_receipts(agent, collector)
90
+ run_tool_through_callbacks(agent, "search", {"q": "x"}, {"hits": []})
91
+ assert seen == [("before", "search")]
92
+ assert collector.step_count == 1
93
+
94
+
95
+ def test_callbacks_never_short_circuit():
96
+ # Every zambo-adk callback must return None: a non-None return
97
+ # would make ADK skip the tool entirely.
98
+ collector = AER1ADKCollector(goal="passthrough")
99
+ agent = attach_receipts(FakeAgent(), collector)
100
+ tool, ctx = FakeTool("t"), FakeContext()
101
+ for cb in agent.before_tool_callback:
102
+ assert cb(tool=tool, args={}, tool_context=ctx) is None
103
+ for cb in agent.after_tool_callback:
104
+ assert cb(tool=tool, args={}, tool_context=ctx, tool_response={}) is None
105
+ for cb in agent.on_tool_error_callback:
106
+ assert (
107
+ cb(tool=tool, args={}, tool_context=ctx, exception=ValueError())
108
+ is None
109
+ )
110
+
111
+
112
+ def test_receipt_hash_commits_to_result():
113
+ c1 = AER1ADKCollector(goal="g")
114
+ a1 = attach_receipts(FakeAgent(), c1)
115
+ run_tool_through_callbacks(a1, "add", {"a": 1}, {"result": 1})
116
+ c2 = AER1ADKCollector(goal="g")
117
+ a2 = attach_receipts(FakeAgent(), c2)
118
+ run_tool_through_callbacks(a2, "add", {"a": 1}, {"result": 999})
119
+ h1 = c1.finalize()["steps"][0]["receipt_hash"]
120
+ h2 = c2.finalize()["steps"][0]["receipt_hash"]
121
+ assert h1 != h2
122
+
123
+
124
+ def test_tampered_receipt_fails_verification():
125
+ collector = AER1ADKCollector(goal="g")
126
+ agent = attach_receipts(FakeAgent(), collector)
127
+ run_tool_through_callbacks(agent, "add", {"a": 1}, {"result": 1})
128
+ receipt = collector.finalize()
129
+ receipt["steps"][0]["tool"] = ""
130
+ assert collector.verify(receipt) != []
131
+
132
+
133
+ def test_merkle_root_matches_recomputed():
134
+ collector = AER1ADKCollector(goal="g")
135
+ agent = attach_receipts(FakeAgent(), collector)
136
+ for i in range(3):
137
+ run_tool_through_callbacks(agent, f"tool_{i}", {}, {"i": i})
138
+ receipt = collector.finalize()
139
+ assert receipt["merkle_root"] == merkle_root(
140
+ [s["receipt_id"] for s in receipt["steps"]]
141
+ )
142
+ assert verify_workflow_receipt(receipt) == []
143
+
144
+
145
+ def test_seq_ordering_and_agent_identity():
146
+ collector = AER1ADKCollector(goal="g")
147
+ state = _CallbackState(collector)
148
+ for i in range(2):
149
+ ctx = FakeContext(agent_name=f"agent_{i}")
150
+ state.begin(FakeTool("t"), {}, ctx)
151
+ state.end(ctx, result={"i": i})
152
+ receipt = collector.finalize()
153
+ assert [s["seq"] for s in receipt["steps"]] == [1, 2]
154
+ assert [s["agent"] for s in receipt["steps"]] == ["agent_0", "agent_1"]
155
+
156
+
157
+ def test_receipt_serializes_to_json():
158
+ collector = AER1ADKCollector(goal="g")
159
+ agent = attach_receipts(FakeAgent(), collector)
160
+ run_tool_through_callbacks(agent, "add", {"a": 1}, {"result": 1})
161
+ text = collector.to_json()
162
+ assert json.loads(text)["type"] == "verifiable-workflow-receipt"
163
+
164
+
165
+ def test_mint_returns_live_audit_url():
166
+ collector = AER1ADKCollector(goal="mint test")
167
+ agent = attach_receipts(FakeAgent(), collector)
168
+ run_tool_through_callbacks(agent, "add", {"a": 40, "b": 2}, {"result": 42})
169
+ receipt = collector.finalize(final_answer="42")
170
+ assert collector.verify(receipt) == []
171
+ url = collector.mint(receipt)
172
+ assert url.startswith("https://zambo.dev/run/")
173
+ print(f"\nlive audit url: {url}")
@@ -0,0 +1,51 @@
1
+ """zambo-adk: Zambo verifiable execution receipts for Google ADK agents.
2
+
3
+ Attach AER-1 receipt recording to any Google Agent Development Kit
4
+ agent. Every tool execution becomes a hash-chained step in a
5
+ verifiable workflow receipt (AER-1, Section 8); finalize() emits the
6
+ receipt, verify() checks it offline, and mint() submits it to
7
+ zambo.dev for a live audit URL.
8
+
9
+ Two integration routes:
10
+
11
+ from zambo_adk import AER1ADKCollector, attach_receipts
12
+
13
+ collector = AER1ADKCollector(goal="Research the launch plan")
14
+ agent = attach_receipts(my_adk_agent, collector)
15
+ # ... run the agent ...
16
+ receipt = collector.finalize(final_answer="done")
17
+ assert collector.verify(receipt) == [] # VALID
18
+ print(collector.mint(receipt)) # live audit URL
19
+
20
+ Or runner-wide, covering every agent under the Runner:
21
+
22
+ from zambo_adk import AER1ADKPlugin
23
+ app = App(name="my_app", root_agent=agent,
24
+ plugins=[AER1ADKPlugin(collector)])
25
+
26
+ The hooks only observe: every callback returns None, so ADK executes
27
+ tools exactly as it would without us. No network calls until you call
28
+ mint(). A recording failure can never break a run.
29
+ """
30
+
31
+ from .collector import (
32
+ AER1ADKCollector,
33
+ ZAMBO_MCP_URL,
34
+ merkle_root,
35
+ verify_workflow_receipt,
36
+ )
37
+ from .hooks import AER1ADKPlugin, attach_receipts
38
+ from .testing import ScriptedModel, scripted_agent
39
+
40
+ __all__ = [
41
+ "AER1ADKCollector",
42
+ "AER1ADKPlugin",
43
+ "attach_receipts",
44
+ "ScriptedModel",
45
+ "scripted_agent",
46
+ "ZAMBO_MCP_URL",
47
+ "merkle_root",
48
+ "verify_workflow_receipt",
49
+ ]
50
+
51
+ __version__ = "0.1.0"