---
name: senior-python-engineering
description: "The standard for writing Python here — small, typed, explicit, testable modules with side effects pushed to the edges. Use when implementing or designing any Python code."
generated: true
source: .ai/skills/senior-python-engineering.md
---

<!--
This file is generated by tools/skills.
Do not edit this file directly.
Edit .ai/skills/senior-python-engineering.md and run `make generate-ai`.
-->

# Skill: Senior Python Engineering

## Purpose

Set the standard for writing Python in this repository: small, typed,
explicit, testable modules with side effects pushed to the edges — code that
future agents can safely modify without fear.

## When to Use This Skill

- Implementing any Python code in `src/agentplatform/` (tasks `0001`+).
- Designing a new module, model, or adapter.
- Deciding between dataclass vs. Pydantic, sync vs. async, or where to put a
  dependency.

## Files to Read First

- `AGENTS.md` (architecture rules and planned package layout).
- `docs/ARCHITECTURE.md` (layer responsibilities).
- `.ai/skills/architecture-review.md` (invariants you must not break).
- `pyproject.toml` (configured tools: ruff, mypy, pytest).

## Core Principles

- **Small modules, single responsibility.** One layer concern per module; no
  giant files.
- **Explicit typed models.** Type everything; `mypy` runs strict.
  - Use **Pydantic** for data crossing a boundary that needs validation/parsing
    (YAML spec models, plugin/API payloads).
  - Use **dataclasses** (often frozen) for internal domain models that don't
    need runtime validation (e.g. compiled graph nodes). Choose intentionally,
    not at random.
- **Separate domain models from transport/API models.** Don't reuse a FastAPI
  request model as your internal domain object.
- **Keep side effects at boundaries.** I/O, network, time, and randomness live
  in adapters at the edges; core logic stays pure and testable.
- **Dependency injection over hidden globals.** Pass collaborators in; no
  module-level mutable singletons, and never mutable global request state.
- **Adapters via explicit interfaces.** External integrations (LLM, MCP, DB,
  plugins) sit behind ABCs so implementations are navigable and can be faked in
  tests. Reach for `typing.Protocol` where the typing is structural rather than
  nominal: `runtime_checkable` Protocols for *optional* capabilities detected
  with `isinstance` (see `agent_engine/engine/`), plain Protocols for the
  minimal shape a function needs of its argument.
- **Clear async boundaries.** Don't mix blocking I/O into async paths; keep
  async at the edges and be consistent within a module.
- **Typed, actionable errors.** Define specific exception types; messages name
  the offending thing (key, id, variable). Avoid bare `except`.
- **Small, intent-named functions.** A function name should describe what it
  does; if you need "and" in the name, split it.
- **Avoid clever code and premature abstraction.** Write the simple version;
  abstract only when a second real case appears.

## Process

1. **Locate the layer.** Put code in the correct `src/agentplatform/<layer>`
   package per `AGENTS.md`.
2. **Model the data.** Decide Pydantic (boundary/validated) vs. dataclass
   (internal/domain); make domain models immutable where possible.
3. **Define the interface first.** Sketch the public function/class signatures
   and any ABC for external dependencies — or a `runtime_checkable` Protocol
   when the capability is optional and detected structurally at runtime.
4. **Implement the core pure logic**, keeping I/O behind injected adapters.
5. **Wire dependencies explicitly** (constructor/params), not via globals.
6. **Type and lint:** run `make format` then `make lint` (ruff + mypy).
7. **Add tests** per `.ai/skills/testing.md`; run `make check`.

## Expected tools

- `pytest` — tests.
- `ruff` — formatting + linting.
- `mypy` (or `pyright` if later configured) — static typing.
- `pydantic` — boundary/validated models.
- `fastapi` — when the API layer is implemented (task `0009`).
- `typer` — when the CLI is implemented (task `0008`).

## Checklist Before Finishing

- [ ] Code is in the correct layer; modules are small and single-purpose.
- [ ] Everything is typed; `mypy` is clean.
- [ ] Pydantic vs. dataclass chosen intentionally and justified.
- [ ] Domain models separated from transport/API models.
- [ ] Side effects isolated at adapters; core logic is pure.
- [ ] Dependencies injected; no hidden/mutable globals; no global request state.
- [ ] External integrations sit behind explicit ABCs; optional capabilities
      behind `runtime_checkable` Protocols.
- [ ] Errors are typed and actionable.
- [ ] No premature abstraction or clever code.
- [ ] `make check` passes.

## Common Mistakes to Avoid

- One big module that spans several layers.
- Untyped code or `Any` used to silence `mypy`.
- Reusing transport models as domain models (or vice versa).
- Hidden global state / module-level singletons holding request data.
- Concrete external clients hardwired in core logic (untestable).
- Building frameworks/abstractions before there's a second use case.

## Expected Final Report

State: which modules were added/changed and the layer each belongs to; the
model choices (Pydantic vs. dataclass) and why; how external dependencies are
injected and faked; confirmation that side effects are at the edges; and the
`make check` result.
