Skip to content

Latest commit

 

History

History
808 lines (601 loc) · 15.5 KB

File metadata and controls

808 lines (601 loc) · 15.5 KB

Development Workflow

This document provides a complete step-by-step workflow for developing amplifier-foundation modules from initial design through publishing and maintenance.

Table of Contents

  1. Define Module Responsibility
  2. Write Contract (README First)
  3. Create Module Structure
  4. Implement Protocol
  5. Local Testing Strategies
  6. Testing Workflow
  7. Publishing to GitHub
  8. Versioning and Tagging
  9. User Consumption Patterns
  10. Maintenance and Updates

1. Define Module Responsibility

Before writing any code, clearly define what your module does.

The Single Responsibility Test

Ask yourself: "Can I describe this module in one sentence?"

Good examples (single responsibility):

  • "Convert text to uppercase"
  • "Log agent events to a file"
  • "Search files for regex patterns"
  • "Store conversation history in SQLite"

Bad examples (too broad):

  • "Handle all file operations" → Split into: read, write, search tools
  • "Manage everything about conversations" → Split into: context, search, export
  • "Do AI stuff" → Not specific enough

Responsibility Checklist

Before proceeding, answer these questions:

  • Can I describe this module in one sentence?
  • Does it do exactly one thing well?
  • Would splitting it make it more useful?
  • Can I test it independently?
  • Will users understand what it does from the name?

Naming Your Module

Follow the convention: amplifier-module-{type}-{name}

Type prefix:

  • tool- for tools
  • hook- for hooks
  • provider- for providers
  • context- for contexts
  • loop- for orchestrators

Name should be:

  • Descriptive (what it does)
  • Kebab-case
  • No redundant prefixes (not tool-file-tool)

Examples:

  • amplifier-module-tool-filesystem
  • amplifier-module-hook-logging
  • amplifier-module-provider-anthropic
  • amplifier-module-context-memory

2. Write Contract (README First)

The README.md is your public contract. Write it before implementing code.

README Template

# amplifier-module-{type}-{name}

> One-sentence description of what this module does

## Installation

\`\`\`bash
uv pip install git+https://github.com/yourusername/amplifier-module-{type}-{name}.git
\`\`\`

## Features

- Feature 1
- Feature 2
- Feature 3

## Usage

### Basic Usage

\`\`\`yaml
# In your profile.md or bundle.md
{type}s:
  - git+https://github.com/yourusername/amplifier-module-{type}-{name}.git@v1.0.0
\`\`\`

### Configuration

\`\`\`yaml
{type}_config:
  option1: value1
  option2: value2
\`\`\`

## API

### mount(coordinator, config) -> dict

**Returns**: Dict with the following functions/objects:

#### function_name(arg1: type, arg2: type) -> return_type

Description of what this function does.

**Arguments**:
- `arg1`: Description
- `arg2`: Description

**Returns**: Description of return value

**Raises**:
- `ExceptionType`: When this happens

**Example**:
\`\`\`python
tools = await mount(coordinator, config={})
result = await tools["function_name"](arg1="value", arg2=42)
\`\`\`

## Configuration Reference

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| option1 | str | "default" | What this does |
| option2 | int | 100 | What this does |

## Examples

### Example 1: Common Use Case

\`\`\`python
# Code example
\`\`\`

### Example 2: Advanced Usage

\`\`\`python
# Code example
\`\`\`

## Testing

\`\`\`bash
uv pip install pytest pytest-asyncio
uv run pytest tests/
\`\`\`

## License

MIT

Contract Principles

  1. Be specific: Exact type signatures, not vague descriptions
  2. Show examples: Real code that users can copy
  3. Document all options: Every config key with type and default
  4. Explain errors: What exceptions can be raised and why
  5. Include examples: At least 2-3 usage examples

3. Create Module Structure

Create the directory structure and boilerplate files.

Directory Creation

# Create module directory
mkdir amplifier-module-{type}-{name}
cd amplifier-module-{type}-{name}

# Create package directory (underscores)
mkdir amplifier_module_{type}_{name}

# Create tests directory
mkdir tests

# Create GitHub Actions directory
mkdir -p .github/workflows

Create pyproject.toml

[project]
name = "amplifier-module-{type}-{name}"
version = "0.1.0"
description = "One sentence description"
readme = "README.md"
requires-python = ">=3.11"
license = { text = "MIT" }
authors = [
    { name = "Your Name", email = "your.email@example.com" }
]
dependencies = [
    "amplifier-foundation>=0.1.0",
    # Add other dependencies here
]

[project.optional-dependencies]
dev = [
    "pytest>=7.4.0",
    "pytest-asyncio>=0.21.0",
    "pytest-cov>=4.1.0",
]

# Entry point registration
[project.entry-points."amplifier.{type}s"]  # Note: plural
{name} = "amplifier_module_{type}_{name}:mount"

# For tool modules: also register schema
[project.entry-points."amplifier.tool_schemas"]  # Only for tools
{name} = "amplifier_module_{type}_{name}:get_schema"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"
python_functions = "test_*"
asyncio_mode = "auto"

[tool.coverage.run]
source = ["amplifier_module_{type}_{name}"]
omit = ["tests/*", "*/__pycache__/*"]

[tool.coverage.report]
exclude_lines = [
    "pragma: no cover",
    "def __repr__",
    "raise AssertionError",
    "raise NotImplementedError",
    "if __name__ == .__main__.:",
    "if TYPE_CHECKING:",
]

Create Package init.py

# amplifier_module_{type}_{name}/__init__.py
"""
amplifier-module-{type}-{name}

One sentence description.
"""
from typing import Any

__version__ = "0.1.0"

async def mount(coordinator: Any, config: dict) -> dict[str, Any]:
    """Mount the module.

    Args:
        coordinator: The amplifier coordinator
        config: Configuration dictionary

    Returns:
        Dict with module functions
    """
    # TODO: Implement
    pass

# For tool modules, also implement:
def get_schema() -> dict:
    """Return JSON schema for tool functions.

    Returns:
        Dict mapping function names to schemas
    """
    # TODO: Implement
    pass

Create py.typed Marker

touch amplifier_module_{type}_{name}/py.typed

Create Test Structure

# tests/conftest.py
import pytest

@pytest.fixture
async def mounted_module():
    """Fixture that mounts the module."""
    from amplifier_module_{type}_{name} import mount
    return await mount(coordinator=None, config={})

@pytest.fixture
def sample_config():
    """Sample configuration for tests."""
    return {
        "option1": "value1",
        "option2": 42
    }
# tests/test_unit.py
import pytest

@pytest.mark.asyncio
async def test_mount_succeeds():
    """Test that module mounts successfully."""
    from amplifier_module_{type}_{name} import mount
    module = await mount(coordinator=None, config={})
    assert isinstance(module, dict)

# Add more tests here

Create .gitignore

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
.venv/
ENV/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Testing
.pytest_cache/
.coverage
htmlcov/
.tox/

# IDEs
.vscode/
.idea/
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db

Create GitHub Actions Workflow

# .github/workflows/test.yml
name: Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.11", "3.12"]

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install uv
        uses: astral-sh/setup-uv@v1

      - name: Install dependencies
        run: |
          uv pip install -e ".[dev]"

      - name: Run tests with coverage
        run: |
          uv run pytest --cov --cov-report=xml --cov-report=term

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage.xml

4. Implement Protocol

Now implement the mount() function and any required methods.

Implementation Pattern

# amplifier_module_{type}_{name}/__init__.py
from typing import Any
from ._internal import Implementation  # Private implementation

async def mount(coordinator: Any, config: dict) -> dict[str, Any]:
    """Mount the module."""
    # Validate config
    _validate_config(config)

    # Create implementation instance
    impl = Implementation(config)

    # Initialize resources
    await impl.initialize()

    # Return public interface
    return {
        "function1": impl.function1,
        "function2": impl.function2,
    }

def _validate_config(config: dict):
    """Validate configuration (private helper)."""
    required = ["option1"]
    for key in required:
        if key not in config:
            raise ValueError(f"Missing required config: {key}")
# amplifier_module_{type}_{name}/_internal.py
"""Private implementation details."""

class Implementation:
    """Private implementation class."""

    def __init__(self, config: dict):
        self._config = config
        self._cache = {}

    async def initialize(self):
        """Initialize resources."""
        # Setup connections, load data, etc.
        pass

    async def function1(self, arg: str) -> str:
        """Public function."""
        return self._process(arg)

    def _process(self, arg: str) -> str:
        """Private helper method."""
        return arg.upper()

5. Local Testing Strategies

Test your module locally before publishing.

Strategy 1: Quick Test with Environment Variable

# Set environment variable to your local module
export AMPLIFIER_MODULE_TOOL_MYMODULE=$(pwd)

# Test with a simple profile
cat > test_profile.md << 'EOF'
---
tools:
  - mymodule
---

# Test Profile
EOF

# Run test
python -c "
from amplifier_foundation import load_bundle, create_session
import asyncio

async def test():
    bundle = await load_bundle('./test_profile.md')
    session = await create_session(bundle)
    print('Module loaded successfully!')

asyncio.run(test())
"

Strategy 2: Development Workspace with file:// Sources

Create a workspace configuration:

# workspace.yaml
name: module-dev-workspace
python: "3.11"

sources:
  - file://./amplifier-module-tool-mymodule  # Your local module
  - git+https://github.com/microsoft/amplifier-foundation.git  # Core

Install workspace:

uv pip install -e ./amplifier-module-tool-mymodule
uv pip install amplifier-foundation

Strategy 3: Dev Profile Pattern

Create a dev profile that uses your local module:

# dev_profile.md
---
tools:
  - file://./amplifier-module-tool-mymodule
provider:
  name: anthropic
  config:
    model: claude-3-5-sonnet
---

# Development Profile

This profile uses the local development version of the module.

Test:

python -c "
from amplifier_foundation import load_bundle
import asyncio
asyncio.run(load_bundle('./dev_profile.md'))
"

6. Testing Workflow

Follow the test pyramid: 60% unit, 30% integration, 10% e2e.

Run Unit Tests

uv run pytest tests/test_unit.py -v

Run Integration Tests

uv run pytest tests/test_integration.py -v

Run All Tests with Coverage

uv run pytest --cov=amplifier_module_{type}_{name} --cov-report=html
open htmlcov/index.html

Check Coverage Targets

uv run pytest --cov --cov-report=term

# Should show:
# Coverage: 85% (target)
# Critical paths: 100%

7. Publishing to GitHub

Initialize Git Repository

git init
git add .
git commit -m "feat: initial module implementation

- Implement mount() function
- Add tests with 85% coverage
- Document API in README

🤖 Generated with Claude Code
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"

Create GitHub Repository

gh repo create amplifier-module-{type}-{name} --public
git push -u origin main

Add Repository Description

gh repo edit --description "One sentence description of module"
gh repo edit --add-topic amplifier-module
gh repo edit --add-topic amplifier-{type}

8. Versioning and Tagging

Follow Semantic Versioning:

  • Major (1.0.0): Breaking changes
  • Minor (0.1.0): New features, backward compatible
  • Patch (0.0.1): Bug fixes

Create Initial Release

# Update version in pyproject.toml
# version = "0.1.0"

git add pyproject.toml
git commit -m "chore: bump version to 0.1.0"
git tag v0.1.0
git push origin main --tags

Create GitHub Release

gh release create v0.1.0 \
  --title "Initial Release v0.1.0" \
  --notes "First stable release

Features:
- Feature 1
- Feature 2
- Feature 3

📦 Install:
\`\`\`yaml
tools:
  - git+https://github.com/yourusername/amplifier-module-{type}-{name}.git@v0.1.0
\`\`\`"

9. User Consumption Patterns

Pattern 1: Git URL Reference

Users reference your module by git URL:

# profile.md
---
tools:
  - git+https://github.com/yourusername/amplifier-module-tool-mymodule.git@v0.1.0
---

Pattern 2: Local Development

During development, users can use local paths:

# profile.md
---
tools:
  - file://./amplifier-module-tool-mymodule
---

Pattern 3: Bundle Composition

Users can compose your module into bundles:

# bundle.md
---
tools:
  - git+https://github.com/yourusername/amplifier-module-tool-mymodule.git@v0.1.0
  - git+https://github.com/otheruser/amplifier-module-tool-another.git@v1.2.0
---

10. Maintenance and Updates

Update Workflow

  1. Create feature branch:

    git checkout -b feat/new-feature
  2. Make changes and update tests

  3. Run tests:

    uv run pytest --cov
  4. Update README if API changed

  5. Commit changes:

    git add .
    git commit -m "feat: add new feature"
  6. Push and create PR:

    git push origin feat/new-feature
    gh pr create
  7. After merge, tag new version:

    git checkout main
    git pull
    # Update version in pyproject.toml
    git commit -m "chore: bump version to 0.2.0"
    git tag v0.2.0
    git push origin main --tags
    gh release create v0.2.0

Breaking Changes

When making breaking changes:

  1. Bump major version (e.g., 0.5.0 → 1.0.0)
  2. Document migration in CHANGELOG.md
  3. Provide migration examples
  4. Consider deprecation period

Summary Checklist

Before considering a module "done":

  • Responsibility is single and clear
  • README documents complete API
  • pyproject.toml has correct entry points
  • Tests achieve 85% coverage
  • Tests follow 60/30/10 pyramid
  • Local testing succeeds
  • Examples in README work
  • Code passes linting
  • Git repository created
  • Tagged with semantic version
  • GitHub release published
  • Repository has description and topics

Next Steps