Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 24 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Build

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
workflow_dispatch:

jobs:
python-build:
name: Python Build & Test (${{ matrix.python-version }})
strategy:
matrix:
python-version: ['3.11', '3.12']
uses: ./.github/workflows/reusable-python.yml
with:
python-version: ${{ matrix.python-version }}
install-extras: test,typing
extra-packages: dbus-python keyring secretstorage
run-tests: true
test-command: pytest tests/ --cov=agent_codemode --cov-report term-missing
run-mypy: true
mypy-target: agent_codemode
44 changes: 44 additions & 0 deletions .github/workflows/py-code-style.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Py Code Style

on:
push:
branches:
- main
- develop
paths:
- 'agent_codemode/**/*.py'
- 'tests/**/*.py'
- 'pyproject.toml'
- 'setup.py'
- '.pre-commit-config.yaml'
- 'ruff.toml'
- '.ruff.toml'
- '.github/workflows/py-code-style.yml'
- '.github/workflows/reusable-python.yml'
pull_request:
branches:
- main
- develop
paths:
- 'agent_codemode/**/*.py'
- 'tests/**/*.py'
- 'pyproject.toml'
- 'setup.py'
- '.pre-commit-config.yaml'
- 'ruff.toml'
- '.ruff.toml'
- '.github/workflows/py-code-style.yml'
- '.github/workflows/reusable-python.yml'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
check-code-style:
uses: ./.github/workflows/reusable-python.yml
with:
python-version: '3.11'
extra-packages: pre-commit
run-pre-commit: true
43 changes: 43 additions & 0 deletions .github/workflows/py-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Py Tests

on:
push:
branches:
- main
paths:
- 'agent_codemode/**'
- 'tests/**'
- 'pyproject.toml'
- 'setup.py'
- 'requirements*.txt'
- '**/*.py'
- '.github/workflows/py-tests.yml'
- '.github/workflows/reusable-python.yml'
pull_request:
branches:
- main
paths:
- 'agent_codemode/**'
- 'tests/**'
- 'pyproject.toml'
- 'setup.py'
- 'requirements*.txt'
- '**/*.py'
- '.github/workflows/py-tests.yml'
- '.github/workflows/reusable-python.yml'

workflow_dispatch:

jobs:
tests:
strategy:
max-parallel: 1
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13']
uses: ./.github/workflows/reusable-python.yml
with:
python-version: ${{ matrix.python-version }}
install-extras: test
extra-packages: dbus-python keyring secretstorage
run-tests: true
test-command: pytest tests/ --cov=agent_codemode --cov-report term-missing
44 changes: 44 additions & 0 deletions .github/workflows/py-typing.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Py Type Checking

on:
push:
branches:
- main
- develop
paths:
- 'agent_codemode/**/*.py'
- 'tests/**/*.py'
- 'pyproject.toml'
- 'setup.py'
- 'mypy.ini'
- '.mypy.ini'
- '.github/workflows/py-typing.yml'
- '.github/workflows/reusable-python.yml'
pull_request:
branches:
- main
- develop
paths:
- 'agent_codemode/**/*.py'
- 'tests/**/*.py'
- 'pyproject.toml'
- 'setup.py'
- 'mypy.ini'
- '.mypy.ini'
- '.github/workflows/py-typing.yml'
- '.github/workflows/reusable-python.yml'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
type-check:
uses: ./.github/workflows/reusable-python.yml
with:
python-version: '3.12'
install-extras: typing
extra-packages: types-requests
run-mypy: true
mypy-target: agent_codemode
100 changes: 100 additions & 0 deletions .github/workflows/reusable-python.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Reusable Python Workflow

on:
workflow_call:
inputs:
python-version:
description: Python version to use
required: false
type: string
default: '3.11'
install-system-deps:
description: Install Linux system dependencies and unlock keyring
required: false
type: boolean
default: true
install-extras:
description: Optional extras to install from pyproject (for example test,typing)
required: false
type: string
default: ''
extra-packages:
description: Optional extra packages to install with uv pip
required: false
type: string
default: ''
run-tests:
description: Run tests
required: false
type: boolean
default: false
test-command:
description: Test command to run when run-tests=true
required: false
type: string
default: 'pytest -q'
run-mypy:
description: Run mypy
required: false
type: boolean
default: false
mypy-target:
description: Package/module path to type-check
required: false
type: string
default: ''
run-pre-commit:
description: Run pre-commit
required: false
type: boolean
default: false

jobs:
python-checks:
runs-on: ubuntu-latest

steps:
- name: Install system dependencies
if: inputs.install-system-deps
run: |
sudo apt-get update
sudo apt-get install -y libdbus-1-3 libdbus-1-dev libglib2.0-dev

- name: Checkout
uses: actions/checkout@v6

- name: Setup UV
uses: astral-sh/setup-uv@v7
with:
version: latest
python-version: ${{ inputs.python-version }}
activate-environment: true

- name: Install dependencies
run: |
uv pip install .
if [[ -n "${{ inputs.install-extras }}" ]]; then
uv pip install ".[${{ inputs.install-extras }}]"
fi
if [[ -n "${{ inputs.extra-packages }}" ]]; then
uv pip install ${{ inputs.extra-packages }}
fi

- name: Unlock keyring
if: inputs.install-system-deps
uses: t1m0thyj/unlock-keyring@v1

- name: Configure git to use https
run: git config --global hub.protocol https

- name: Run tests
if: inputs.run-tests
run: ${{ inputs.test-command }}

- name: Run mypy
if: inputs.run-mypy
run: mypy ${{ inputs.mypy-target }}

- name: Run pre-commit
if: inputs.run-pre-commit
run: pre-commit run --all-files
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Same task, same MCP server — Code Mode uses significantly fewer tokens by comp
|--------|-------------|
| `allow_direct_tool_calls` | When `False` (default), `call_tool` is hidden; all execution flows through `execute_code` |
| `max_tool_calls` | Safety cap limiting tool invocations per `execute_code` run |
| `sandbox_variant` | Sandbox type for code execution (default: `"local-eval"`) |
| `sandbox_variant` | Sandbox type for code execution (default: `"eval"`) |
| `workspace_path` | Working directory for sandbox execution |
| `generated_path` | Path where tool bindings are generated |
| `skills_path` | Path for saved skills |
Expand Down Expand Up @@ -461,6 +461,29 @@ When running in a sandbox, state can persist between `execute_code` calls within
- [Advanced Tool Use](https://www.anthropic.com/engineering/advanced-tool-use) - Anthropic
- [Programmatic MCP Prototype](https://github.com/domdomegg/programmatic-mcp-prototype)

## CI Workflows

This repository uses a reusable GitHub Actions workflow at `.github/workflows/reusable-python.yml`.

The following workflows call it:

- `.github/workflows/build.yml`
- `.github/workflows/py-tests.yml`
- `.github/workflows/py-code-style.yml`
- `.github/workflows/py-typing.yml`

Reusable workflow inputs:

- `python-version`: Python version to run.
- `install-system-deps`: Install Linux dependencies and unlock keyring.
- `install-extras`: Extras from `pyproject.toml` (for example `test,typing`).
- `extra-packages`: Additional packages installed with `uv pip install`.
- `run-tests`: Enable test execution.
- `test-command`: Command used for tests.
- `run-mypy`: Enable mypy.
- `mypy-target`: Package or module passed to mypy.
- `run-pre-commit`: Enable pre-commit checks.

## License

BSD 3-Clause License
2 changes: 1 addition & 1 deletion agent_codemode/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

"""Agent Codemode."""

__version__ = "0.1.1"
__version__ = "0.1.3"
14 changes: 7 additions & 7 deletions agent_codemode/composition/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def set_skills_metadata(self, metadata: list[dict[str, Any]]) -> None:
self._skills_metadata = metadata

def _is_local_eval_sandbox(self) -> bool:
"""Check if the sandbox is a local-eval type (has in-memory namespaces).
"""Check if the sandbox is a eval type (has in-memory namespaces).

This checks the actual sandbox instance, not the config, to handle
cases where an external sandbox is passed that differs from config.
Expand Down Expand Up @@ -202,7 +202,7 @@ async def _setup_sandbox_environment(self) -> None:
# Use /tmp so 'from generated.mcp...' works (files are at /tmp/generated/)
sandbox_generated_path = "/tmp"
else:
# For local-eval, use the parent directory so 'from generated.mcp...' works
# For eval, use the parent directory so 'from generated.mcp...' works
# The generated_path might be './generated', we need its parent on sys.path
sandbox_generated_path = str(generated_path.parent)

Expand Down Expand Up @@ -705,7 +705,7 @@ def generate_skills_in_sandbox(self) -> None:
skill scripts **directly** in the Jupyter kernel, reading the script
files from the shared ``skills_path`` on disk. This avoids the
HTTP proxy round-trip (call_tool → MCP proxy → agent-runtimes →
local-eval fallback) which caused blocking and deadlocks.
eval fallback) which caused blocking and deadlocks.

The skills_path is the same between the agent-runtimes process and
the Jupyter runtime (shared filesystem or mount).
Expand All @@ -717,7 +717,7 @@ def generate_skills_in_sandbox(self) -> None:
if self._sandbox is None or not self._skills_metadata:
return

# Skip for local-eval sandboxes (they use the on-disk generated files)
# Skip for eval sandboxes (they use the on-disk generated files)
if self._is_local_eval_sandbox():
return

Expand Down Expand Up @@ -1032,7 +1032,7 @@ async def execute(

# Get the generated path for sys.path setup
# For remote sandboxes, use /tmp so 'from generated.mcp...' works (files at /tmp/generated/)
# For local-eval, use parent of generated_path so 'from generated.mcp...' works
# For eval, use parent of generated_path so 'from generated.mcp...' works
# Use actual sandbox type detection, not config
is_local_eval = self._is_local_eval_sandbox()
if not is_local_eval:
Expand Down Expand Up @@ -1086,7 +1086,7 @@ async def execute(
'''
# Branch based on actual sandbox type (already computed above)
if is_local_eval:
# For local-eval, we can access _namespaces directly
# For eval, we can access _namespaces directly
return await self._execute_local_eval(code, setup_code, timeout)
else:
# For Jupyter/remote sandboxes, use run_code()
Expand All @@ -1099,7 +1099,7 @@ async def _execute_local_eval(
setup_code: str,
timeout: Optional[float] = None,
) -> ExecutionResult:
"""Execute code in local-eval sandbox with direct namespace access."""
"""Execute code in eval sandbox with direct namespace access."""
import sys
import io
import time
Expand Down
2 changes: 1 addition & 1 deletion agent_codemode/toolset.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ class CodemodeToolset(AbstractToolset):

registry: ToolRegistry | None = None
config: CodeModeConfig = field(default_factory=CodeModeConfig)
sandbox: Any | None = None # Optional pre-configured sandbox (e.g., LocalEvalSandbox)
sandbox: Any | None = None # Optional pre-configured sandbox (e.g., EvalSandbox)
allow_direct_tool_calls: bool | None = None
allow_discovery_tools: bool = True
tool_reranker: Callable[[list, str, Optional[str]], Awaitable[list]] | None = None
Expand Down
2 changes: 1 addition & 1 deletion agent_codemode/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ class CodeModeConfig(BaseModel):
workspace_path: str = "./workspace"
skills_path: str = "./skills"
generated_path: str = "./generated"
sandbox_variant: str = "local-eval"
sandbox_variant: str = "eval"
sandbox_image: str | None = None
allow_direct_tool_calls: bool = False
max_tool_calls: int | None = None
Expand Down
4 changes: 2 additions & 2 deletions docs/docs/skills/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,10 @@ For Pydantic AI agents, use the `AgentSkillsToolset`:
```python
from pydantic_ai import Agent
from agent_skills import AgentSkillsToolset, SandboxExecutor
from code_sandboxes import LocalEvalSandbox
from code_sandboxes.eval_sandbox import EvalSandbox

# Create toolset with sandbox execution
sandbox = LocalEvalSandbox()
sandbox = EvalSandbox()
toolset = AgentSkillsToolset(
directories=["./skills"],
executor=SandboxExecutor(sandbox),
Expand Down
Loading
Loading