Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f98c652
added isolated code executor
AlexFierro9 Oct 20, 2025
b3cdd32
Update src/google/adk/code_executors/isolated_code_executor.py
AlexFierro9 Oct 20, 2025
a77b0c2
Update src/google/adk/code_executors/isolated_code_executor.py
AlexFierro9 Oct 20, 2025
8fbd73b
added the units tests
AlexFierro9 Oct 20, 2025
9cf3ef0
Merge branch 'main' into main
AlexFierro9 Oct 21, 2025
98e5a83
Merge branch 'main' into main
AlexFierro9 Oct 22, 2025
ff06b44
Merge branch 'main' into main
hangfei Oct 23, 2025
ba5dbbd
Merge branch 'main' into main
AlexFierro9 Oct 24, 2025
b0f3f6c
run autoformat.sh
AlexFierro9 Oct 24, 2025
1e49154
Merge branch 'main' of https://github.com/AlexFierro9/adk-python
AlexFierro9 Oct 24, 2025
5d01c91
Merge branch 'main' into main
AlexFierro9 Oct 25, 2025
6424fc4
Merge remote-tracking branch 'upstream/main'
AlexFierro9 Oct 30, 2025
c1e3487
Merge branch 'main' into main
hangfei Nov 5, 2025
039b9b9
Added Isolated code executor as a config option for unsafe local code…
AlexFierro9 Nov 9, 2025
0dc3c26
Merge branch 'main' of https://github.com/AlexFierro9/adk-python
AlexFierro9 Nov 9, 2025
6a9ee6b
Added Isolated Code Executor as a separate config
AlexFierro9 Nov 9, 2025
1cc3a2e
Merge branch 'main' into main
AlexFierro9 Nov 9, 2025
7603e57
got rid of isolated code executor file, added tests cases (with assis…
AlexFierro9 Nov 9, 2025
bf19756
Merge branch 'main' of https://github.com/AlexFierro9/adk-python
AlexFierro9 Nov 9, 2025
8f12cc6
Merge branch 'main' into main
AlexFierro9 Nov 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/google/adk/code_executors/isolated_code_executor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from __future__ import annotations

import sys
import subprocess

from pydantic import Field
from typing_extensions import override

from ..agents.invocation_context import InvocationContext
from .base_code_executor import BaseCodeExecutor
from .code_execution_utils import CodeExecutionInput
from .code_execution_utils import CodeExecutionResult


class IsolatedCodeExecutor(BaseCodeExecutor):
"""A code executor that executes code in an isolated process.

This provides memory isolation from the main application, but it is not a
full security sandbox. The executed code runs with the same permissions as the
main application and can access the filesystem, network, etc.
"""

# Overrides the BaseCodeExecutor attribute: this executor cannot be stateful.
stateful: bool = Field(default=False, frozen=True, exclude=True)

# Overrides the BaseCodeExecutor attribute: this executor cannot
# optimize_data_file.
optimize_data_file: bool = Field(default=False, frozen=True, exclude=True)

def __init__(self, **data):
"""Initializes the IsolatedCodeExecutor."""
if 'stateful' in data and data['stateful']:
raise ValueError('Cannot set `stateful=True` in IsolatedCodeExecutor.')
if 'optimize_data_file' in data and data['optimize_data_file']:
raise ValueError(
'Cannot set `optimize_data_file=True` in IsolatedCodeExecutor.'
)
super().__init__(**data)

@override
def execute_code(
self,
invocation_context: InvocationContext,
code_execution_input: CodeExecutionInput,
) -> CodeExecutionResult:
# Executes code by spawning a new python interpreter process.
code = code_execution_input.code
process_result = subprocess.run(
[sys.executable, "-c", code],
capture_output=True,
text=True
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Consider adding a timeout to the subprocess.run call to prevent indefinite hanging in case the executed code enters an infinite loop or takes too long to execute. This will improve the robustness of the executor.

Also, it might be useful to capture and log the return code of the subprocess for debugging purposes.

    process_result = subprocess.run(
    [sys.executable, "-c", code],
    capture_output=True,
    text=True, # Enables decoding of stdout and stderr as text
    timeout=30 # Add a timeout to prevent indefinite hanging
    )

    if process_result.returncode != 0:
      print(f"Code execution failed with return code: {process_result.returncode}")



return CodeExecutionResult(
stdout=process_result.stdout,
stderr=process_result.stderr,
output_files=[],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

It's important to handle potential exceptions that might occur during the code execution within the subprocess. For example, the code might raise an exception that isn't properly propagated back to the main process. Consider adding a try-except block around the subprocess.run call to catch and handle such exceptions, providing more informative error messages in the CodeExecutionResult.

Suggested change
return CodeExecutionResult(
stdout=process_result.stdout,
stderr=process_result.stderr,
output_files=[],
try:
process_result = subprocess.run(
[sys.executable, "-c", code],
capture_output=True,
text=True
)
except subprocess.TimeoutExpired as e:
return CodeExecutionResult(
stdout="",
stderr=f"Code execution timed out: {e}",
output_files=[],
)
except Exception as e:
return CodeExecutionResult(
stdout="",
stderr=f"Code execution failed: {e}",
output_files=[],
)

)
112 changes: 112 additions & 0 deletions tests/unittests/code_executors/test_isolated_code_executor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from unittest.mock import MagicMock

from google.adk.agents.base_agent import BaseAgent
from google.adk.agents.invocation_context import InvocationContext
from google.adk.code_executors.code_execution_utils import CodeExecutionInput
from google.adk.code_executors.code_execution_utils import CodeExecutionResult
from google.adk.code_executors.isolated_code_executor import IsolatedCodeExecutor
from google.adk.sessions.base_session_service import BaseSessionService
from google.adk.sessions.session import Session
import pytest
import os


@pytest.fixture
def mock_invocation_context() -> InvocationContext:
"""Provides a mock InvocationContext."""
mock_agent = MagicMock(spec=BaseAgent)
mock_session = MagicMock(spec=Session)
mock_session_service = MagicMock(spec=BaseSessionService)
return InvocationContext(
invocation_id="test_invocation",
agent=mock_agent,
session=mock_session,
session_service=mock_session_service,
)


class TestIsolatedCodeExecutor:

def test_init_default(self):
executor = IsolatedCodeExecutor()
assert not executor.stateful
assert not executor.optimize_data_file

def test_init_stateful_raises_error(self):
with pytest.raises(
ValueError,
match="Cannot set `stateful=True` in IsolatedCodeExecutor.",
):
IsolatedCodeExecutor(stateful=True)

def test_init_optimize_data_file_raises_error(self):
with pytest.raises(
ValueError,
match=(
"Cannot set `optimize_data_file=True` in IsolatedCodeExecutor."
),
):
IsolatedCodeExecutor(optimize_data_file=True)

def test_execute_code_simple_print(
self, mock_invocation_context: InvocationContext
):
executor = IsolatedCodeExecutor()
code_input = CodeExecutionInput(code='print("hello world")')
result = executor.execute_code(mock_invocation_context, code_input)

assert isinstance(result, CodeExecutionResult)
assert result.stdout == "hello world\n"
assert result.stderr == ""
assert result.output_files == []

def test_execute_code_with_error(
self, mock_invocation_context: InvocationContext
):
executor = IsolatedCodeExecutor()
code_input = CodeExecutionInput(code='raise ValueError("Test error")')
result = executor.execute_code(mock_invocation_context, code_input)

assert isinstance(result, CodeExecutionResult)
assert result.stdout == ""
assert "Test error" in result.stderr

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test asserts that "Test error" is in result.stderr. It would be more robust to assert that the ValueError exception type is also present in the stderr. This ensures that the correct exception is being raised and captured.

Suggested change
assert "Test error" in result.stderr
assert "ValueError" in result.stderr
assert "Test error" in result.stderr

assert result.output_files == []

def test_execute_code_variable_assignment(
self, mock_invocation_context: InvocationContext
):
executor = IsolatedCodeExecutor()
code_input = CodeExecutionInput(code="x = 10\nprint(x * 2)")
result = executor.execute_code(mock_invocation_context, code_input)

assert result.stdout == "20\n"
assert result.stderr == ""

def test_execute_code_empty(self, mock_invocation_context: InvocationContext):
executor = IsolatedCodeExecutor()
code_input = CodeExecutionInput(code="")
result = executor.execute_code(mock_invocation_context, code_input)
assert result.stdout == ""
assert result.stderr == ""

def test_execute_code_with_import(
self, mock_invocation_context: InvocationContext
):
executor = IsolatedCodeExecutor()
code = "import os; print(os.linesep)"
code_input = CodeExecutionInput(code=code)
result = executor.execute_code(mock_invocation_context, code_input)

assert result.stdout.strip() == os.linesep.strip()
assert result.stderr == ""

def test_execute_code_multiline_output(
self, mock_invocation_context: InvocationContext
):
executor = IsolatedCodeExecutor()
code = 'print("line 1")\nprint("line 2")'
code_input = CodeExecutionInput(code=code)
result = executor.execute_code(mock_invocation_context, code_input)

assert result.stdout == "line 1\nline 2\n"
assert result.stderr == ""