adk-code-mode 0.4.0__tar.gz → 1.0.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.
Files changed (80) hide show
  1. adk_code_mode-1.0.0/.release-please-manifest.json +3 -0
  2. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/CHANGELOG.md +18 -0
  3. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/PKG-INFO +56 -111
  4. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/README.md +53 -108
  5. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/pyproject.toml +2 -2
  6. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/sandbox-wheel/src/adk_code_mode_sandbox/__about__.py +1 -1
  7. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/__about__.py +1 -1
  8. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/__init__.py +2 -9
  9. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/_artifact_tools.py +1 -1
  10. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/output.py +4 -3
  11. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/runtime/base.py +9 -8
  12. adk_code_mode-0.4.0/src/adk_code_mode/executor.py → adk_code_mode-1.0.0/src/adk_code_mode/tool.py +373 -433
  13. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/tool_result_artifacts.py +5 -5
  14. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/tools/catalog.py +3 -24
  15. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/tools/normaliser.py +1 -1
  16. adk_code_mode-1.0.0/src/adk_code_mode/workspace/__init__.py +8 -0
  17. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/workspace/files.py +1 -5
  18. adk_code_mode-1.0.0/tests/conftest.py +28 -0
  19. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_agent_with_fake_llm.py +85 -33
  20. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_catalog.py +1 -13
  21. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_docker_integration.py +22 -24
  22. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_host_loop.py +2 -2
  23. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_integration.py +164 -242
  24. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_output.py +2 -0
  25. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_remote_integration.py +50 -55
  26. adk_code_mode-1.0.0/tests/test_tool.py +166 -0
  27. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_tool_result_artifacts.py +20 -17
  28. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_workspace_files.py +0 -5
  29. adk_code_mode-0.4.0/.release-please-manifest.json +0 -3
  30. adk_code_mode-0.4.0/src/adk_code_mode/callback.py +0 -81
  31. adk_code_mode-0.4.0/src/adk_code_mode/workspace/__init__.py +0 -8
  32. adk_code_mode-0.4.0/tests/conftest.py +0 -37
  33. adk_code_mode-0.4.0/tests/test_callback.py +0 -147
  34. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/.githooks/pre-commit +0 -0
  35. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/.githooks/pre-push +0 -0
  36. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/.github/workflows/ci.yml +0 -0
  37. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/.github/workflows/release-please.yml +0 -0
  38. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/.gitignore +0 -0
  39. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/.prettierrc +0 -0
  40. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/.vscode/settings.json +0 -0
  41. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/.zed/settings.json +0 -0
  42. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/AGENTS.md +0 -0
  43. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/CLAUDE.md +0 -0
  44. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/LICENSE +0 -0
  45. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/Makefile +0 -0
  46. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/docker/Dockerfile +0 -0
  47. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/release-please-config.json +0 -0
  48. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/sandbox-wheel/README.md +0 -0
  49. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/sandbox-wheel/pyproject.toml +0 -0
  50. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/sandbox-wheel/src/adk_code_mode_sandbox/__init__.py +0 -0
  51. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/sandbox-wheel/src/adk_code_mode_sandbox/__main__.py +0 -0
  52. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/sandbox-wheel/src/adk_code_mode_sandbox/_entry.py +0 -0
  53. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/sandbox-wheel/src/adk_code_mode_sandbox/_http_server.py +0 -0
  54. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/sandbox-wheel/src/adk_code_mode_sandbox/_rpc_client.py +0 -0
  55. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/sandbox-wheel/src/adk_code_mode_sandbox/protocol.py +0 -0
  56. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/scripts/install-git-hooks.sh +0 -0
  57. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/py.typed +0 -0
  58. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/runtime/__init__.py +0 -0
  59. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/runtime/docker.py +0 -0
  60. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/runtime/protocol.py +0 -0
  61. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/runtime/remote.py +0 -0
  62. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/tools/__init__.py +0 -0
  63. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/tools/dispatcher.py +0 -0
  64. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/tools/namespacing.py +0 -0
  65. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/src/adk_code_mode/tools/stubs.py +0 -0
  66. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/__init__.py +0 -0
  67. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/_docker_helpers.py +0 -0
  68. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/_fake_runtime.py +0 -0
  69. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_artifact_tools.py +0 -0
  70. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_dispatcher.py +0 -0
  71. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_docker_image.py +0 -0
  72. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_namespacing.py +0 -0
  73. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_normaliser.py +0 -0
  74. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_protocol.py +0 -0
  75. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_remote_backend.py +0 -0
  76. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_runtime_docker.py +0 -0
  77. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_sandbox_rpc_client.py +0 -0
  78. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_stubs.py +0 -0
  79. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/tests/test_version.py +0 -0
  80. {adk_code_mode-0.4.0 → adk_code_mode-1.0.0}/uv.lock +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "1.0.0"
3
+ }
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.0](https://github.com/a2anet/adk-code-mode/compare/adk-code-mode-v0.4.1...adk-code-mode-v1.0.0) (2026-07-15)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * `CodeModeCodeExecutor`, `code_mode_before_model_callback`, and `CODE_MODE_SYSTEM_INSTRUCTION` no longer exist. Add `ExecuteCodeTool` to the agent's `tools=[...]` instead of wiring `code_executor=`; drop `before_model_callback=`, `generate_content_config=`, and `CODE_MODE_SYSTEM_INSTRUCTION` entirely; `release_invocation` is now `async`. See the README's migration section.
9
+
10
+ ### Features
11
+
12
+ * replace `CodeModeCodeExecutor` with `ExecuteCodeTool` ([#10](https://github.com/a2anet/adk-code-mode/issues/10)) ([b78572e](https://github.com/a2anet/adk-code-mode/commit/b78572e72446930548760756255390d1a3cc388a))
13
+
14
+ ## [0.4.1](https://github.com/a2anet/adk-code-mode/compare/adk-code-mode-v0.4.0...adk-code-mode-v0.4.1) (2026-07-10)
15
+
16
+
17
+ ### Documentation
18
+
19
+ * note the sandbox writes to `/tools` in remote deployment guide ([96bb45e](https://github.com/a2anet/adk-code-mode/commit/96bb45e4e18d9d54709f4c10b6b09cf3567ee385))
20
+
3
21
  ## [0.4.0](https://github.com/a2anet/adk-code-mode/compare/adk-code-mode-v0.3.0...adk-code-mode-v0.4.0) (2026-07-09)
4
22
 
5
23
 
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: adk-code-mode
3
- Version: 0.4.0
4
- Summary: A "Code Mode" Code Executor for ADK for agents to interact with tools, files, and custom packages with Python
3
+ Version: 1.0.0
4
+ Summary: A "Code Mode" sandboxed code-execution tool for ADK agents to interact with tools, files, and custom packages with Python
5
5
  Project-URL: Documentation, https://github.com/A2ANet/adk-code-mode#readme
6
6
  Project-URL: Issues, https://github.com/A2ANet/adk-code-mode/issues
7
7
  Project-URL: Source, https://github.com/A2ANet/adk-code-mode
8
8
  Author-email: A2A Net <hello@a2anet.com>
9
9
  License-Expression: Apache-2.0
10
10
  License-File: LICENSE
11
- Keywords: a2a,adk,agents,code-executor,code-mode
11
+ Keywords: a2a,adk,agents,code-mode,execute-code
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Programming Language :: Python
14
14
  Classifier: Programming Language :: Python :: 3.10
@@ -27,12 +27,12 @@ Description-Content-Type: text/markdown
27
27
 
28
28
  # ADK Code Mode
29
29
 
30
- A [Code Mode](https://blog.cloudflare.com/code-mode/) code executor for [Agent Development Kit (ADK)](https://github.com/google/adk-python).
30
+ A [Code Mode](https://blog.cloudflare.com/code-mode/) sandboxed code-execution tool for [Agent Development Kit (ADK)](https://github.com/google/adk-python).
31
31
 
32
- The `CodeModeCodeExecutor` allows ADK agents to write Python code to call tools and read and write files.
32
+ `ExecuteCodeTool` lets ADK agents write Python code to call tools and read and write files.
33
33
  Code runs inside a sandboxed container, and tools (and their credentials) are executed on the host.
34
34
  The base image comes with the stdlib and can be extended with any Python package you want.
35
- It also supports `input_files` and `output_files`, and the sandboxed container can list, load, and save ADK Artifacts.
35
+ The sandboxed container can list, load, and save ADK Artifacts, and files it creates are returned as artifacts.
36
36
 
37
37
  Inspired by Cloudflare's [Code Mode](https://blog.cloudflare.com/code-mode/) and Anthropic's [Code execution with MCP](https://www.anthropic.com/engineering/code-execution-with-mcp).
38
38
 
@@ -40,22 +40,22 @@ Inspired by Cloudflare's [Code Mode](https://blog.cloudflare.com/code-mode/) and
40
40
 
41
41
  - **Call ADK tools from sandbox code** — imports against the `tools` package proxy back to the host and run through ADK's `before_tool` / `after_tool` / `on_error` callbacks and the plugin manager exactly as direct tool calls would.
42
42
  - **Bake any Python package into the image** — extend the published base image with anything the model's code needs to `import`, no runtime `pip install` required.
43
- - **Cross-turn persistence via ADK Artifacts** — `save_artifact` / `load_artifact` / `list_artifacts` are auto-injected and route through your configured `ArtifactService`.
43
+ - **Cross-turn persistence via ADK Artifacts** — `save_artifact` / `load_artifact` / `list_artifacts` are auto-injected and route through your configured `ArtifactService`. Files the code creates or changes are saved as artifacts automatically too.
44
44
  - **Tool results saved as artifacts** — on by default; every tool's result is persisted as a `code_mode.tool_result` artifact (with optional model-supplied name/description) so hosts can forward outputs and large results stay out of the prompt. Opt out with `save_tool_results_as_artifacts=False`.
45
45
  - **Bounded stdout/stderr** — overflow lands in a session artifact instead of poisoning the prompt.
46
- - **Production-ready remote sandbox** — `RemoteBackend` connects to an isolated per-turn container over WebSocket, reused across the turn's code blocks. Deploy on any cloud platform (Cloud Run, Fargate, ACI, Kubernetes, Fly.io, etc.).
46
+ - **Production-ready remote sandbox** — `RemoteBackend` connects to an isolated per-turn container over WebSocket, reused across the turn's `execute_code` calls. Deploy on any cloud platform (Cloud Run, Fargate, ACI, Kubernetes, Fly.io, etc.).
47
47
  - **Local development** — `UnsafeLocalDockerBackend` runs the sandbox against your local Docker daemon for fast iteration. **Not for production** — see [Safety](#-safety).
48
48
 
49
49
  | | BuiltIn | AgentEngineSandbox | VertexAi | Container | Gke | CodeMode |
50
- | ----------------------------------- | ------- | ------------------------------- | ------------------------------- | --------- | --- | ------------------------ |
51
- | Call ADK tools from code | no | no | no | no | no | yes (with limitations) |
52
- | Extra Python packages | no | no (more than stdlib but fixed) | no (more than stdlib but fixed) | yes | yes | yes |
53
- | Variables are stateful | no | yes | yes | no | no | yes (within a turn) |
54
- | Input files | no | yes | yes | no | no | yes |
55
- | Output files | no | yes | yes | no | no | yes |
56
- | Storage | no | yes (via variables) | yes (via variables) | no | no | yes (via ADK Artifacts) |
57
- | Local development version available | no | no | no | yes | yes | yes |
58
- | Bounded stdout/stderr | no | no | no | no | no | yes (`max_output_chars`) |
50
+ | ----------------------------------- | ------- | -------------------------------- | -------------------------------- | --------- | --- | ------------------------ |
51
+ | Call ADK tools from code | no | no | no | no | no | yes (with limitations) |
52
+ | Extra Python packages | no | no (more than stdlib but fixed) | no (more than stdlib but fixed) | yes | yes | yes |
53
+ | Variables are stateful | no | yes | yes | no | no | yes (within a turn) |
54
+ | Input files | no | yes | yes | no | no | no (use Artifacts) |
55
+ | Output files | no | yes | yes | no | no | yes (as Artifacts) |
56
+ | Storage | no | yes (via variables) | yes (via variables) | no | no | yes (via ADK Artifacts) |
57
+ | Local development version available | no | no | no | yes | yes | yes |
58
+ | Bounded stdout/stderr | no | no | no | no | no | yes (`max_output_chars`) |
59
59
 
60
60
  ## 📦 Install
61
61
 
@@ -79,26 +79,15 @@ Requires Python 3.10+. Local development requires [Docker](https://docs.docker.c
79
79
 
80
80
  ## 🚀 Usage
81
81
 
82
- Build a `CodeModeCodeExecutor`, then wire four things into the agent:
83
-
84
- - **`CODE_MODE_SYSTEM_INSTRUCTION`** — append to the agent's `instruction`. Teaches the model how to write code blocks and use artifacts.
85
- - **`code_mode_before_model_callback`** — set as `before_model_callback`. Injects the tool catalog (`<code-mode>` block) into the system prompt on every model turn.
86
- - **`generate_content_config`** with `function_calling_config.mode="NONE"` — disables native function calling so the model writes Python instead of attempting tool calls that fail with `MALFORMED_FUNCTION_CALL` (since `tools=[]`).
87
- - **`release_invocation`** — call it from the agent's `after_agent_callback` to release the turn's sandbox container as soon as the turn ends. An idle reaper (`session_idle_timeout_seconds`, default `600`) is a backstop, so containers are still reclaimed without it — just later.
82
+ Build an `ExecuteCodeTool`, give it the tools the sandboxed code may call, and attach it to your agent. Wire `release_invocation` into `after_agent_callback` to release the turn's sandbox container when the turn ends. An idle reaper (`session_idle_timeout_seconds`, default `600`) is a backstop.
88
83
 
89
84
  ### Production (remote sandbox)
90
85
 
91
86
  ```python
92
87
  from google.adk.agents import LlmAgent
93
- from google.genai import types as genai_types
94
- from adk_code_mode import (
95
- CODE_MODE_SYSTEM_INSTRUCTION,
96
- CodeModeCodeExecutor,
97
- RemoteBackend,
98
- code_mode_before_model_callback,
99
- )
88
+ from adk_code_mode import ExecuteCodeTool, RemoteBackend
100
89
 
101
- executor = CodeModeCodeExecutor(
90
+ tool = ExecuteCodeTool(
102
91
  tools=[my_fn_tool, McpToolset(...), OpenAPIToolset(...)],
103
92
  backend=RemoteBackend(
104
93
  url="https://sandbox-xyz.run.app", # your deployed sandbox URL
@@ -106,21 +95,14 @@ executor = CodeModeCodeExecutor(
106
95
  ),
107
96
  )
108
97
 
109
- def _release_sandbox(callback_context):
110
- executor.release_invocation(callback_context.invocation_id)
98
+ async def _release_sandbox(callback_context):
99
+ await tool.release_invocation(callback_context.invocation_id)
111
100
 
112
101
  root_agent = LlmAgent(
113
102
  name="assistant",
114
- model="gemini-2.5-pro",
115
- instruction=f"You are a helpful assistant.\n\n{CODE_MODE_SYSTEM_INSTRUCTION}",
116
- tools=[], # do NOT also bind tools here; the executor owns them.
117
- code_executor=executor,
118
- generate_content_config=genai_types.GenerateContentConfig(
119
- tool_config=genai_types.ToolConfig(
120
- function_calling_config=genai_types.FunctionCallingConfig(mode="NONE"),
121
- ),
122
- ),
123
- before_model_callback=code_mode_before_model_callback(executor),
103
+ model="gemini-3.5-flash",
104
+ instruction="You are a helpful assistant.",
105
+ tools=[tool],
124
106
  after_agent_callback=[_release_sandbox],
125
107
  )
126
108
  ```
@@ -130,14 +112,9 @@ root_agent = LlmAgent(
130
112
  > **`UnsafeLocalDockerBackend` is not safe for production or multi-tenant use.** See [Safety](#-safety).
131
113
 
132
114
  ```python
133
- from adk_code_mode import (
134
- CODE_MODE_SYSTEM_INSTRUCTION,
135
- CodeModeCodeExecutor,
136
- UnsafeLocalDockerBackend,
137
- code_mode_before_model_callback,
138
- )
115
+ from adk_code_mode import ExecuteCodeTool, UnsafeLocalDockerBackend
139
116
 
140
- executor = CodeModeCodeExecutor(
117
+ tool = ExecuteCodeTool(
141
118
  tools=[my_fn_tool, McpToolset(...), OpenAPIToolset(...)],
142
119
  backend=UnsafeLocalDockerBackend(image="ghcr.io/a2anet/adk-code-mode:latest"),
143
120
  )
@@ -217,9 +194,9 @@ RemoteBackend(
217
194
 
218
195
  The same pattern works on any platform that runs Docker containers as HTTP services (AWS Fargate/ECS, Azure Container Instances, Kubernetes, Fly.io, etc.):
219
196
 
220
- 1. **One container per turn.** Each container handles exactly one turn (one or more code blocks) and exits.
197
+ 1. **One container per turn.** Each container handles exactly one turn (one or more `execute_code` calls) and exits.
221
198
  2. **Block all outbound network access.** Without egress restrictions, user code can exfiltrate data, access cloud metadata endpoints, or scan internal networks.
222
- 3. **Set a read-only root filesystem** where the platform supports it (e.g., `readOnlyRootFilesystem: true` in Kubernetes). The sandbox only writes to `/workspace`.
199
+ 3. **Keep `/workspace` and `/tools` writable.** The sandbox stages the working directory and materialises the `tools` package into `/tools` at connect time. If you set a read-only root filesystem (e.g., `readOnlyRootFilesystem: true` in Kubernetes), mount both as writable volumes (e.g., an `emptyDir`).
223
200
  4. **Authenticate connections.** Set `ADK_CODE_MODE_AUTH_TOKEN` and layer platform-level auth (IAM, NetworkPolicy, security groups) on top.
224
201
 
225
202
  Required env vars:
@@ -252,9 +229,9 @@ RemoteBackend(
252
229
 
253
230
  Code Mode exposes two file surfaces:
254
231
 
255
- - **`/workspace`** — the turn's working directory. It persists across the turn's code blocks and resets between turns. ADK `input_files` for a block are staged here before that block runs (`open("input.csv")` works). Files whose content changed in a block are returned as that block's `CodeExecutionResult.output_files`; nothing under `/workspace` is re-hydrated on the next turn unless persisted via `save_artifact`.
232
+ - **The working directory** — the turn's workspace. It persists across the turn's `execute_code` calls and resets between turns. Files created or changed by a call are collected afterward and saved as session artifacts automatically, returned to the model as a list of filenames (reloadable via `load_artifact`) nothing is re-hydrated into the working directory on the next turn unless the model explicitly loads it back with `load_artifact`.
256
233
 
257
- - **ADK Artifacts** — persistent cross-turn storage. `CodeModeCodeExecutor` injects three tools into the catalog:
234
+ - **ADK Artifacts** — persistent cross-turn storage. `ExecuteCodeTool` injects three tools into the sandbox:
258
235
 
259
236
  ```python
260
237
  import json
@@ -275,10 +252,11 @@ Pass `include_artifact_tools=False` to opt out. To react when the model saves an
275
252
 
276
253
  ```python
277
254
  async def on_saved(invocation_context, delta):
278
- # delta is {filename: version} for everything saved this turn.
255
+ # delta is {filename: version} for everything the sandbox-side save_artifact
256
+ # calls (or wrapped tool results) saved this turn.
279
257
  ...
280
258
 
281
- CodeModeCodeExecutor(tools=..., backend=..., on_artifacts_saved=on_saved)
259
+ ExecuteCodeTool(tools=..., backend=..., on_artifacts_saved=on_saved)
282
260
  ```
283
261
 
284
262
  ### Tool results as artifacts
@@ -288,7 +266,7 @@ By default (`save_tool_results_as_artifacts=True`) every non-artifact tool is wr
288
266
  Naming is transparent: the filename is derived from the tool name and call id. Each wrapped tool also gains two **optional** parameters — `artifact_name` and `artifact_description` — that the model may pass to name/describe the saved artifact; both land in the artifact's `custom_metadata` (`code_mode.artifact_name` / `code_mode.artifact_description`) alongside the marker. Set `save_tool_results_as_artifacts=False` to return tool results inline without persisting them.
289
267
 
290
268
  ```python
291
- CodeModeCodeExecutor(tools=..., backend=..., save_tool_results_as_artifacts=True)
269
+ ExecuteCodeTool(tools=..., backend=..., save_tool_results_as_artifacts=True)
292
270
  ```
293
271
 
294
272
  ## 🐳 Sandbox Image
@@ -304,80 +282,52 @@ The same image works for both `RemoteBackend` and `UnsafeLocalDockerBackend`. To
304
282
 
305
283
  ## ⚙️ Configuration
306
284
 
307
- All settings are `CodeModeCodeExecutor` constructor arguments:
285
+ All settings are `ExecuteCodeTool` constructor arguments:
308
286
 
309
287
  | Argument | Default | Purpose |
310
288
  | --- | --- | --- |
311
- | `max_catalog_chars` | `50_000` | Soft cap on the rendered tool catalog in the system prompt. When exceeded, the per-tool sections are replaced with a short note telling the model how to navigate `/tools/` from Python. |
312
- | `max_output_chars` | `50_000` | Caps stdout/stderr handed back to the model. Overflow is saved as a session artifact at `code_mode/stdout/<execution-id>.txt` and the model sees a head-and-tail view pointing to it. |
289
+ | `append_function_stubs_to_system_instruction` | `True` | Appends a `<code-mode>` block listing available function signatures and docstrings to the system instruction on every model turn. If the rendered catalog would exceed `max_catalog_chars`, the model can still discover functions by listing `/tools/` and reading the generated stubs. |
290
+ | `max_catalog_chars` | `50_000` | Maximum size of the appended function catalog. |
291
+ | `max_output_chars` | `50_000` | Caps stdout/stderr handed back to the model. Overflow is saved as a session artifact at `code_mode/stdout/<call-id>.txt` and the model sees a head-and-tail view pointing to it. |
313
292
  | `max_code_chars` | `1_000_000` | Rejects oversized code payloads before starting a container. |
314
- | `timeout_seconds` | `None` | Caps overall execution time. Defaults to the platform request timeout (e.g. Cloud Run `--timeout`); set explicitly for defense in depth. |
315
- | `per_tool_timeout_seconds` | `None` | Caps each individual tool call. |
293
+ | `timeout_seconds` | `None` | Caps overall execution time of one `execute_code` call. Defaults to the platform request timeout (e.g. Cloud Run `--timeout`); set explicitly for defense in depth. |
294
+ | `per_tool_timeout_seconds` | `None` | Caps each individual tool call made from within the sandbox. |
316
295
  | `session_idle_timeout_seconds` | `600` | Idle reaper: closes a turn's container once it goes untouched this long. Backstop for turns that never call `release_invocation`. |
317
296
 
318
297
  The model can read spilled stdout back from the overflow artifact:
319
298
 
320
299
  ```python
321
300
  from tools import load_artifact
322
- spilled = load_artifact(filename="code_mode/stdout/<execution-id>.txt")
301
+ spilled = load_artifact(filename="code_mode/stdout/<call-id>.txt")
323
302
  print(spilled["data"][-2000:])
324
303
  ```
325
304
 
326
305
  ### Turn-scoped sessions
327
306
 
328
- A sandbox container is held open for one **turn** (one ADK invocation) and reused across that turn's code blocks, so cold start is paid at most once per turn. Python globals **and** `/workspace` persist across a turn's blocks and reset between turns — use ADK Artifacts for cross-turn persistence. The container is released when the turn ends (via `release_invocation`, wired in [Usage](#-usage)) and destroyed by the platform, so no state survives into another turn or tenant.
307
+ A sandbox container is held open for one **turn** (one ADK invocation) and reused across that turn's `execute_code` calls, so cold start is paid at most once per turn. Python globals **and** the working directory persist across a turn's calls and reset between turns — use ADK Artifacts for cross-turn persistence. The container is released when the turn ends (via `await tool.release_invocation(...)`, wired in [Usage](#-usage)) and destroyed by the platform, so no state survives into another turn or tenant.
329
308
 
330
309
  ## 🏗️ Architecture
331
310
 
332
- **Host wheel (`adk-code-mode`).** Lives in the same process as your `LlmAgent`. The `before_model_callback` resolves tools, renders the catalog, and appends it to the system prompt. At execution time, it generates a `tools/` Python package of thin stubs, stages the block's `input_files` into `/workspace`, and opens (or reuses) the turn's sandbox connection — the container spans the whole turn.
311
+ **Host wheel (`adk-code-mode`).** Lives in the same process as your `LlmAgent`. `ExecuteCodeTool.process_llm_request` resolves tools and, when `append_function_stubs_to_system_instruction` is enabled, renders the catalog and appends it to the system instruction. At execution time (`run_async`), it generates a `tools/` Python package of thin stubs, stages the working directory, and opens (or reuses) the turn's sandbox connection — the container spans the whole turn.
333
312
 
334
313
  **Sandbox wheel (`adk-code-mode-sandbox`).** Pre-installed in the container image. When model code calls a stub, it sends a JSON-Lines frame over the control connection; the host runs the real tool (with callbacks and plugins) and sends the result back.
335
314
 
336
315
  The only things crossing the boundary are: code, tool call arguments, tool return values, and log frames.
337
316
 
338
317
  | Backend | Transport | Multi-tenant safe? | When to use |
339
- | -------------------------- | ---------------------- | ------------------ | ------------------------------- |
318
+ | -------------------------- | ---------------------- | ------------------- | -------------------------------- |
340
319
  | `RemoteBackend` | WebSocket over HTTPS | **Yes** | Production — any cloud platform |
341
- | `UnsafeLocalDockerBackend` | TCP over Docker bridge | No | Local development only |
320
+ | `UnsafeLocalDockerBackend` | TCP over Docker bridge | No | Local development only |
342
321
 
343
322
  ### What the model sees
344
323
 
345
- Your `instruction` (containing `CODE_MODE_SYSTEM_INSTRUCTION`) followed by a `<code-mode>` block appended by the callback:
324
+ `execute_code` has a single `code: string` parameter. With `append_function_stubs_to_system_instruction` enabled, the system instruction also gets a `<code-mode>` reference catalog on every turn:
346
325
 
347
326
  ~~~
348
327
  …your instruction…
349
328
 
350
- # How to execute code and use tools
351
- You have no callable function tools. To use a tool you must write Python code in a fenced Python block (i.e. ```python\n...\n```). Do not emit a function or tool call.
352
- The Python Standard Library and a set of custom tools are available to you as an importable library, listed in the `<code-mode>` section below.
353
- To see the result of your code, you need to print it. For example, if you had the following tool:
354
-
355
- ```
356
- <code-mode>
357
-
358
- from tools.slack import send_message
359
-
360
- def send_message(*, channel: str, text: str, thread_ts: str | None = ...) -> Any:
361
- """Send a message to a Slack channel."""
362
- ...
363
-
364
- </code-mode>
365
- ```
366
-
367
- To call the tool, you should write:
368
-
369
- ````
370
- ```python
371
- from tools.slack import send_message
372
-
373
- print(send_message(channel="C123", text="hi"))
374
- ```
375
- ````
376
-
377
- # How to use files and variables in between executions
378
- You can reuse variables and files within a turn. Between turns, to reuse variables and files you must save and load Artifacts.
379
- To list available Artifacts, use the `list_artifacts` tool, to save an Artifact, use the `save_artifact` tool, and to load an Artifact, use the `load_artifact` tool.
380
- Tool results are automatically saved as Artifacts.
329
+ Reference catalog of the Python functions available inside the execute_code sandbox.
330
+ Import these functions from the `tools` package in the code you pass to execute_code.
381
331
 
382
332
  <code-mode>
383
333
 
@@ -403,17 +353,12 @@ from tools import save_artifact, load_artifact, list_artifacts
403
353
 
404
354
  With `save_tool_results_as_artifacts` enabled (the default), each non-artifact tool above — e.g. `list_channels` and `send_message` — also carries two optional `artifact_name: str | None = ...` / `artifact_description: str | None = ...` parameters for naming its saved result.
405
355
 
406
- When the rendered catalog exceeds `max_catalog_chars`, the per-tool sections are replaced with:
356
+ If the rendered catalog would exceed `max_catalog_chars`, nothing is appended to the system instruction that turn — not even a fallback note. The model can still navigate the sandbox from Python:
407
357
 
408
- ```
409
- <code-mode>
410
- A `tools` package is available in the sandbox. List `/tools/` with
411
- `pathlib.Path('/tools').iterdir()`. Each entry is either a `.py` file
412
- (a top-level tool, importable as `from tools import <name>`) or a
413
- subdirectory (a namespace, with tools importable as
414
- `from tools.<namespace> import <name>`). To see a tool's signature and
415
- docstring, read its `.py` file with `open(...).read()`.
416
- </code-mode>
358
+ ```python
359
+ import pathlib
360
+ print(list(pathlib.Path("/tools").iterdir()))
361
+ print(open("/tools/slack/send_message.py").read()) # signature + docstring
417
362
  ```
418
363
 
419
364
  Text and JSON-like MIME types travel as plain strings in artifact tools; binary content is base64-encoded. `load_artifact` returns `{"kind": "text" | "bytes", "data": str, "mime_type": str | None}`.
@@ -424,7 +369,7 @@ Text and JSON-like MIME types travel as plain strings in artifact tools; binary
424
369
 
425
370
  `RemoteBackend` is designed for multi-tenant production use where untrusted users submit arbitrary Python code:
426
371
 
427
- - **One container per turn (one tenant, one invocation).** Within a turn the process/filesystem are reused across that turn's code blocks; the container is destroyed at turn end, with **no cross-turn or cross-tenant sharing**.
372
+ - **One container per turn (one tenant, one invocation).** Within a turn the process/filesystem are reused across that turn's `execute_code` calls; the container is destroyed at turn end, with **no cross-turn or cross-tenant sharing**.
428
373
  - **Environment sanitization.** All env vars are stripped except a safe allowlist (`PATH`, `HOME`, `USER`, locale vars, Python config) before user code runs.
429
374
  - **Credentials never enter the sandbox.** API keys, OAuth tokens, and connection strings stay in the host process. The container only receives tool results.
430
375
  - **Bearer token authentication.** WebSocket connections without a valid token are rejected. Always set `ADK_CODE_MODE_AUTH_TOKEN` and layer platform-level auth on top.
@@ -449,7 +394,7 @@ Named "Unsafe" intentionally: it binds a TCP listener on `0.0.0.0`, communicates
449
394
  ## ⚠️ Limitations
450
395
 
451
396
  - **No credential-requesting tools.** Tools that need ADK to request credentials, confirmations, UI widgets, agent transfer, escalation, or that yield without an immediate response are rejected with a structured error.
452
- - **State is turn-scoped.** Variables and `/workspace` files persist across code blocks **within** a turn, but reset between turns. Use `save_artifact` / `load_artifact` to persist across turns.
397
+ - **State is turn-scoped.** Variables and the working directory persist across `execute_code` calls **within** a turn, but reset between turns. Use `save_artifact` / `load_artifact` to persist across turns.
453
398
  - **No runtime package installation.** The sandbox ships with the Python Standard Library and the runtime's own dependencies only. Extra packages must be baked into the image at build time.
454
399
 
455
400
  ## 🛠️ Development