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
15 changes: 15 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Default owners for everything in the repo
* @xpcmdshell

# Core library
/src/py_code_mode/ @xpcmdshell

# Tests
/tests/ @xpcmdshell

# Documentation
/docs/ @xpcmdshell
/*.md @xpcmdshell

# CI/CD
/.github/ @xpcmdshell
30 changes: 30 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
name: Bug Report
about: Report a bug or unexpected behavior
title: "[Bug] "
labels: bug
assignees: ''
---

## Description
A clear description of what the bug is.

## Steps to Reproduce
1.
2.
3.

## Expected Behavior
What you expected to happen.

## Actual Behavior
What actually happened.

## Environment
- Python version:
- py-code-mode version:
- Executor type (InProcess/Subprocess/Container):
- OS:

## Additional Context
Any other relevant information, logs, or screenshots.
19 changes: 19 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
name: Feature Request
about: Suggest a new feature or enhancement
title: "[Feature] "
labels: enhancement
assignees: ''
---

## Problem Statement
What problem does this feature solve? Why is it needed?

## Proposed Solution
A clear description of what you want to happen.

## Alternatives Considered
Any alternative solutions or features you've considered.

## Additional Context
Any other relevant information, mockups, or examples.
20 changes: 20 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
commit-message:
prefix: "deps"
labels:
- "dependencies"
open-pull-requests-limit: 5

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
commit-message:
prefix: "ci"
labels:
- "ci"
20 changes: 20 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## Summary
Brief description of the changes.

## Changes
-
-

## Type of Change
- [ ] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation update

## Testing
- [ ] Tests pass locally (`uv run pytest`)
- [ ] Linting passes (`uv run ruff check .`)
- [ ] Type checking passes (`uv run mypy src/`)

## Related Issues
Closes #
11 changes: 10 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,13 @@ jobs:
huggingface-${{ runner.os }}-
- run: uv sync --all-extras
# Skip container tests in CI for now (too slow - need Docker layer caching)
- run: uv run pytest tests/ -v --ignore=tests/test_container.py -k "not Container"
- name: Run tests with coverage
run: uv run pytest tests/ -v --ignore=tests/test_container.py -k "not Container" --cov=src/py_code_mode --cov-report=xml --cov-report=term
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
files: ./coverage.xml
fail_ci_if_error: false
verbose: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@ wheels/

# Claude Code
.claude/

# Coverage reports
.coverage
coverage.xml
htmlcov/
18 changes: 18 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-format

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
hooks:
- id: mypy
additional_dependencies:
- types-PyYAML
- types-redis
args: [--strict]
pass_filenames: false
entry: mypy src/
191 changes: 191 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,194 @@ git push -u origin feature/description-of-work
```

---

## Project Overview

**py-code-mode** gives AI agents code execution with persistent skills and tool integration.

The core idea: Agents write Python code. When a workflow succeeds, they save it as a **skill**. Next time, they invoke the skill directly - no re-planning required.

**Python version:** 3.12+ (see `pyproject.toml`)

---

## Directory Structure

```
src/py_code_mode/
cli/ # MCP server and store CLI commands
execution/ # Executors: InProcess, Subprocess, Container
subprocess/ # Jupyter kernel-based subprocess executor
container/ # Docker container executor
in_process/ # Same-process executor
skills/ # Skill storage, library, and vector stores
tools/ # Tool adapters: CLI, MCP, HTTP
adapters/ # CLI, MCP, HTTP adapter implementations
artifacts/ # Artifact storage (file, redis)
deps/ # Dependency management (installer, store)
storage/ # Storage backends (FileStorage, RedisStorage)
integrations/ # Framework integrations (autogen)

tests/ # All tests (pytest)
container/ # Container-specific tests

docs/ # Documentation
examples/ # Example implementations
```

---

## Key Concepts

### Four Namespaces

When agents write code, four namespaces are available:

| Namespace | Purpose |
|-----------|---------|
| `tools.*` | CLI commands, MCP servers, HTTP APIs |
| `skills.*` | Reusable Python workflows with semantic search |
| `artifacts.*` | Persistent data storage across sessions |
| `deps.*` | Runtime Python package management |

### Storage vs Executor

- **Storage** (FileStorage, RedisStorage): Owns skills and artifacts
- **Executor** (InProcess, Subprocess, Container): Owns tools and deps via config

### Executors

| Executor | Use Case |
|----------|----------|
| SubprocessExecutor | Recommended default. Process isolation via Jupyter kernel. |
| ContainerExecutor | Docker isolation for untrusted code. |
| InProcessExecutor | Maximum speed for trusted code. |

---

## Development Commands

### Testing

```bash
# Run all tests (parallel by default via pytest-xdist)
uv run pytest

# Run specific test file
uv run pytest tests/test_skills.py

# Run with verbose output
uv run pytest -v

# Run tests matching pattern
uv run pytest -k "test_skill"

# Run without parallelism (for debugging)
uv run pytest -n 0
```

### Linting and Type Checking

```bash
# Ruff linting
uv run ruff check .

# Ruff with auto-fix
uv run ruff check --fix .

# Type checking
uv run mypy src/

# Run all pre-commit hooks
uv run pre-commit run --all-files
```

### Test Coverage

```bash
# Run tests with coverage report
uv run pytest --cov=src/py_code_mode --cov-report=term

# Coverage threshold is 60% (configured in pyproject.toml)
```

### Running the MCP Server

```bash
# Local development
uv run py-code-mode-mcp --base ~/.code-mode

# With specific storage
uv run py-code-mode-mcp --base ~/.code-mode --redis redis://localhost:6379
```

---

## Architecture Notes

### StorageBackend Protocol

Storage backends implement `StorageBackend` protocol:
- `get_serializable_access()` - For cross-process communication
- `get_skill_library()` - Returns SkillLibrary
- `get_artifact_store()` - Returns ArtifactStore

### Bootstrap Pattern

Cross-process executors (Subprocess, Container) use bootstrap configs to reconstruct namespaces:

```python
# Host process serializes config
storage.get_serializable_access() # -> FileStorageAccess | RedisStorageAccess

# Subprocess/Container reconstructs via bootstrap_namespaces()
```

### Tool Definitions

Tools are defined in YAML files. Key patterns:
- **Escape hatch**: `tools.curl(url="...")` - Full control
- **Recipe**: `tools.curl.get(url="...")` - Pre-configured preset

---

## Common Tasks

### Adding a New Tool Adapter

1. Create adapter in `src/py_code_mode/tools/adapters/`
2. Implement `ToolAdapter` protocol (see `base.py`)
3. Register in `loader.py`

### Adding a New Storage Backend

1. Implement `StorageBackend` protocol in `src/py_code_mode/storage/`
2. Implement serializable access type for cross-process support

### Adding a New Executor

1. Create executor in `src/py_code_mode/execution/`
2. Implement `Executor` protocol (see `protocol.py`)
3. Handle namespace construction via bootstrap

---

## Testing Guidelines

- Use `pytest.mark.asyncio` for async tests (auto mode enabled)
- Container tests are in `tests/container/` - require Docker
- Use `@pytest.mark.xdist_group("group_name")` for tests that need isolation
- Redis tests use testcontainers - spin up automatically

---

## Important Files

| File | Purpose |
|------|---------|
| `src/py_code_mode/session.py` | Main Session API |
| `src/py_code_mode/bootstrap.py` | Namespace reconstruction for subprocesses |
| `src/py_code_mode/execution/protocol.py` | Executor protocol definition |
| `src/py_code_mode/storage/backends.py` | Storage backend implementations |
| `src/py_code_mode/tools/namespace.py` | ToolsNamespace, ToolProxy |
| `src/py_code_mode/skills/library.py` | SkillLibrary implementation |
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![CI](https://github.com/xpcmdshell/py-code-mode/actions/workflows/ci.yml/badge.svg)](https://github.com/xpcmdshell/py-code-mode/actions/workflows/ci.yml)
[![Version](https://img.shields.io/github/v/tag/xpcmdshell/py-code-mode)](https://github.com/xpcmdshell/py-code-mode/tags)
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
[![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Give your AI agents code execution with persistent skills and tool integration.
Expand Down Expand Up @@ -42,7 +42,7 @@ parsed = json.loads(repo_data)
# Save successful workflows as skills
skills.create(
name="fetch_repo_stars",
source="""def run(owner: str, repo: str) -> int:
source="""async def run(owner: str, repo: str) -> int:
import json
data = tools.curl.get(url=f"https://api.github.com/repos/{owner}/{repo}")
return json.loads(data)["stargazers_count"]
Expand Down Expand Up @@ -112,7 +112,7 @@ tools.jq.query(filter=".key", input=json_data)
analysis = skills.invoke("analyze_repo", owner="anthropics", repo="anthropic-sdk-python")

# Skills can build on other skills
def run(repos: list) -> dict:
async def run(repos: list) -> dict:
summaries = [skills.invoke("analyze_repo", **parse_repo(r)) for r in repos]
return {"total": len(summaries), "results": summaries}

Expand Down
Loading
Loading