diff --git a/agents/agent1/Dockerfile b/agents/agent1/Dockerfile new file mode 100644 index 0000000..81a0914 --- /dev/null +++ b/agents/agent1/Dockerfile @@ -0,0 +1,36 @@ +# Minimal container for agent1 installed from a wheel +FROM python:3.12-slim AS builder + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +WORKDIR /app + +# Install build tooling (flit via build) and any system deps if needed +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* +RUN pip install --no-cache-dir build + +# Copy project files (include LICENSE for flit license-files) and build wheel +COPY pyproject.toml README.md LICENSE ./ +COPY src ./src +RUN python -m build --wheel --outdir /tmp/wheels + +FROM python:3.12-slim + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +WORKDIR /app + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Install agent1 from the built wheel +COPY --from=builder /tmp/wheels /tmp/wheels +RUN pip install --no-cache-dir /tmp/wheels/*.whl + +# Default command; override with args +CMD ["agent1", "World"] diff --git a/agents/agent1/README.md b/agents/agent1/README.md index 1042f14..9ef032f 100644 --- a/agents/agent1/README.md +++ b/agents/agent1/README.md @@ -1,19 +1,37 @@ # agent1 -Example agent built from the python-agent-template. Use this as a starting point and replace the description/code with your own agent. +Example agent built from the python-agent-template. Use this as a starting point and replace the description/code with your own agent. For a fuller walkthrough template, see [docs/agent-guide-template.md](../../docs/agent-guide-template.md). -## Quickstart -- From the repo root, set up once: `uv run poe setup` (creates/refreshes `.venv`, installs deps, installs pre-commit hooks). -- Full validation (all agents): `uv run poe check`. -- Faster, agent-scoped runs (from repo root): - - `uv run poe -C agents/agent1 fmt` - - `uv run poe -C agents/agent1 lint` - - `uv run poe -C agents/agent1 pyright` - - `uv run poe -C agents/agent1 mypy` - - `uv run poe -C agents/agent1 bandit` - - `uv run poe -C agents/agent1 test` -- To run only on agents changed by your branch: `python scripts/run_tasks_in_changed_agents.py ` +## Quickstart (typical flow) +- Setup (repo root): `uv run poe setup` (creates/refreshes `.venv`, installs deps, hooks). +- Change into the agent: `cd agents/agent1`. +- Run the agent: `uv run agent1 Alice --greeting hello`. +- Run full checks for this agent: `uv run poe check` (from `agents/agent1`). +- Optional: switch back to root for repo-wide checks: `cd ../.. && uv run poe check`. + +## Common dev tasks (agent-scoped) +- Format: `uv run poe fmt` +- Lint: `uv run poe lint` +- Type check: `uv run poe pyright` / `uv run poe mypy` +- Security scan: `uv run poe bandit` +- Tests: `uv run poe test` + +## Run from repo root (alternative) +- Run the agent without `cd`: `uv run -C agents/agent1 agent1 Alice --greeting hello`. +- Agent checks from root: `uv run -C agents/agent1 poe check`. +- Repo-wide checks: `uv run poe check`. + +## Build the image (wheel-based) +- Build the container (from `agents/agent1`): `docker build -t agent1:latest .`. +- Run the container: `docker run --rm agent1:latest agent1 Bob` (override args as needed). + +## Publish the package to GitHub Packages +- Configure env vars for publishing: + - `export UV_PUBLISH_URL=https://pypi.pkg.github.com/` + - `export UV_PUBLISH_TOKEN=` +- Publish from the agent dir (`agents/agent1`): `uv run poe publish` (uploads the built wheel/sdist). From repo root use `uv run -C agents/agent1 poe publish`. +- Package namespace: `python_agent_template.agents.agent1` uses a namespace root without `__init__.py` so multiple agents can coexist (PyPA guidance: https://packaging.python.org/en/latest/guides/packaging-namespace-packages/). ## Anatomy -- `src/agent1/agent.py` — agent implementation. +- `src/python_agent_template/agents/agent1/agent.py` — agent implementation. - `tests/` — unit tests; extend with PyTest. diff --git a/agents/agent1/pyproject.toml b/agents/agent1/pyproject.toml index 5f1f184..4e97396 100644 --- a/agents/agent1/pyproject.toml +++ b/agents/agent1/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ ] [project.scripts] -agent1 = "agent1.__main__:main" +agent1 = "python_agent_template.agents.agent1.__main__:main" [tool.uv] package = true @@ -70,7 +70,7 @@ no_implicit_optional = true show_error_codes = true [tool.bandit] -targets = ["src/agent1"] +targets = ["src/python_agent_template/agents/agent1"] exclude_dirs = ["tests"] [tool.poe] @@ -79,10 +79,13 @@ include = "../../shared_tasks.toml" [tool.poe.tasks] # Project-specific overrides to scope checks/coverage to this agent; shared tasks remain available for cross-agent runs -bandit = "uv run bandit -c pyproject.toml -r src/agent1" +bandit = "uv run bandit -c pyproject.toml -r src/python_agent_template/agents/agent1" mypy = "uv run mypy --config-file $POE_ROOT/pyproject.toml src" -test = "uv run pytest --cov=agent1 --cov-report=term-missing:skip-covered" +test = "uv run pytest --cov=python_agent_template.agents.agent1 --cov-report=term-missing:skip-covered" [build-system] requires = ["flit-core>=3.12.0,<4"] build-backend = "flit_core.buildapi" + +[tool.flit.module] +name = "python_agent_template.agents.agent1" diff --git a/agents/agent1/src/__init__.py b/agents/agent1/src/__init__.py deleted file mode 100644 index 6332550..0000000 --- a/agents/agent1/src/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from agent1.agent import AgentConfig, ExampleAgent, MissingNameError - -__all__ = ["AgentConfig", "ExampleAgent", "MissingNameError"] diff --git a/agents/agent1/src/agent.py b/agents/agent1/src/agent.py deleted file mode 100644 index 3b4bd57..0000000 --- a/agents/agent1/src/agent.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Minimal example agent to illustrate the template.""" - -from __future__ import annotations - -from dataclasses import dataclass - - -@dataclass -class AgentConfig: - """Configuration for the example agent.""" - - greeting: str = "hello" - - -class ExampleAgent: - """Simple greeter agent.""" - - def __init__(self, config: AgentConfig | None = None) -> None: - """Initialize the agent with optional config.""" - self.config = config or AgentConfig() - - def run(self, name: str) -> str: - """Return a greeting for the provided name.""" - if not name: - raise ValueError("name must be provided") # noqa: TRY003 - return f"{self.config.greeting}, {name}!" diff --git a/agents/agent1/src/agent1/__init__.py b/agents/agent1/src/python_agent_template/agents/agent1/__init__.py similarity index 100% rename from agents/agent1/src/agent1/__init__.py rename to agents/agent1/src/python_agent_template/agents/agent1/__init__.py diff --git a/agents/agent1/src/agent1/__main__.py b/agents/agent1/src/python_agent_template/agents/agent1/__main__.py similarity index 89% rename from agents/agent1/src/agent1/__main__.py rename to agents/agent1/src/python_agent_template/agents/agent1/__main__.py index 2818723..3daa191 100644 --- a/agents/agent1/src/agent1/__main__.py +++ b/agents/agent1/src/python_agent_template/agents/agent1/__main__.py @@ -5,7 +5,7 @@ import argparse import logging -from agent1.agent import AgentConfig, ExampleAgent +from python_agent_template.agents.agent1.agent import AgentConfig, ExampleAgent logger = logging.getLogger(__name__) diff --git a/agents/agent1/src/agent1/agent.py b/agents/agent1/src/python_agent_template/agents/agent1/agent.py similarity index 100% rename from agents/agent1/src/agent1/agent.py rename to agents/agent1/src/python_agent_template/agents/agent1/agent.py diff --git a/agents/agent1/tests/test_agent.py b/agents/agent1/tests/test_agent.py index e6555a3..5bf7aea 100644 --- a/agents/agent1/tests/test_agent.py +++ b/agents/agent1/tests/test_agent.py @@ -2,8 +2,8 @@ import pytest -from agent1 import AgentConfig, ExampleAgent -from agent1.agent import MissingNameError +from python_agent_template.agents.agent1 import AgentConfig, ExampleAgent +from python_agent_template.agents.agent1.agent import MissingNameError def test_run_greets_name() -> None: diff --git a/docs/agent-guide-template.md b/docs/agent-guide-template.md new file mode 100644 index 0000000..dd0e23b --- /dev/null +++ b/docs/agent-guide-template.md @@ -0,0 +1,86 @@ +# Agent Guide Template + +Use this as a blueprint when creating or maintaining any agent in this monorepo. Replace `` with your agent’s name. + +## Principles and conventions +- Isolated per-agent projects under `agents/` to avoid dependency/release coupling. +- `src/` layout to force imports from the installed package and catch packaging/import issues early. +- Namespace per project: `python_agent_template.agents.` with namespace packages (no `__init__.py` at the namespace roots) per PyPA guidance: https://packaging.python.org/en/latest/guides/packaging-namespace-packages/ +- Wheel-first images: build a wheel and install it in the runtime image for reproducibility and smaller layers. +- Tasks via `uv run` + `poe` for consistent env management. + +## Structure (replace ``) +``` +agents// +├─ Dockerfile # wheel-based image build +├─ LICENSE # agent-specific license (included in wheel/sdist) +├─ README.md # quickstart and usage +├─ pyproject.toml # agent metadata, tasks, tooling config, script entrypoint +├─ src/ +│ └─ python_agent_template/ +│ └─ agents/ +│ └─ / +│ ├─ __init__.py # exports +│ ├─ __main__.py # CLI entrypoint +│ └─ agent.py # core logic +└─ tests/ + └─ test_agent.py # unit tests for agent logic +``` + +## Creating a new agent (recipe) +1) Copy `agents/agent1` to `agents/`. +2) Rename package path under `src/python_agent_template/agents/`; update imports. +3) Update `pyproject.toml`: project name/description/URLs, script entrypoint, `tool.flit.module = "python_agent_template.agents."`, bandit target, coverage target. +4) Adjust Dockerfile tags/paths if needed. +5) Update README for the new agent with run/build/publish instructions. +6) Run `uv run poe check` inside `agents/`; optionally run `uv run poe check` from root. + +## Daily workflow +1) Setup once (root): `uv run poe setup`. +2) Work inside the agent: `cd agents/`. +3) Run the CLI: `uv run Alice --greeting hello` (or from root with `uv run -C agents/ ...`). +4) Run agent checks: `uv run poe check`. +5) Before publishing or merging, optionally run repo-wide checks from root: `uv run poe check`. + +## Tasks (agent-scoped) +- `poe fmt` — ruff format +- `poe lint` — ruff lint +- `poe pyright` — strict pyright +- `poe mypy` — strict mypy +- `poe bandit` — security scan on agent code +- `poe test` — pytest with coverage +- `poe check` — bundle of the above +Run with `uv run poe ` from `agents/`, or `uv run -C agents/ poe ` from root. + +## Running the agent +- From agent dir: `uv run Alice --greeting hello` +- From root: `uv run -C agents/ Alice --greeting hello` + +## Testing and coverage +- Unit tests: `uv run poe test` +- The CLI guard `if __name__ == "__main__":` is often marked `# pragma: no cover` because it only runs as a script; add a CLI test and drop the pragma if you want it counted. + +## Lint, types, security +- Format then lint: `uv run poe fmt` and `uv run poe lint` +- Types: `uv run poe pyright` and `uv run poe mypy` +- Security: `uv run poe bandit` + +## Build the container (wheel-based) +- Build: `docker build -t :latest .` +- Run: `docker run --rm :latest Bob` +- Rationale: builder stage creates a wheel; runtime installs the wheel for reproducibility and a smaller attack surface. + +## Publish to GitHub Packages +- Set: + - `export UV_PUBLISH_URL=https://pypi.pkg.github.com/` + - `export UV_PUBLISH_TOKEN=` +- From agent dir: `uv run poe publish` (uploads wheel/sdist for this agent), or from root: `uv run -C agents/ poe publish`. + +## What lives where +- Agent-level (agents/): code, tests, Dockerfile, agent-specific tasks/config, LICENSE, README, built artifacts. +- Project-level (root): shared tasks, root `pyproject.toml`, shared scripts (`scripts/`), CI, global lint/type/test settings. + +## Troubleshooting +- uv missing: install via `curl -LsSf https://astral.sh/uv/install.sh | sh`. +- Publish auth errors: confirm `UV_PUBLISH_TOKEN` and `UV_PUBLISH_URL` point to your owner. +- Import errors: use `uv run ...` and correct cwd (`agents/` or `-C agents/`). diff --git a/shared_tasks.toml b/shared_tasks.toml index dae1615..7513013 100644 --- a/shared_tasks.toml +++ b/shared_tasks.toml @@ -10,9 +10,12 @@ lint = "ruff check" pyright = "pyright" bandit = "sh -c 'ROOT=$(git -C \"$POE_ROOT\" rev-parse --show-toplevel); uv run bandit -c \"$ROOT/pyproject.toml\" -r \"$ROOT/agents\" \"$ROOT/scripts\"'" +# publish builds and uploads the package to the configured index (e.g., GitHub Packages) publish = "uv publish" +# repo/agent check bundle; when run with -C agents/, scopes to that agent +check = ["fmt", "lint", "pyright", "mypy", "bandit", "test"] + clean-dist = "rm -rf dist" build-package = "uv build" -move-dist = "sh -c 'mkdir -p ../../dist && mv dist/* ../../dist/ 2>/dev/null || true'" -build = ["build-package", "move-dist"] +build = ["build-package"]