| 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 |
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 Protocols/interfaces. External integrations (LLM, MCP, DB,
plugins) sit behind
typing.Protocol/ABCs so they can be faked in tests.
- 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
- Locate the layer. Put code in the correct
src/agentplatform/<layer>
package per AGENTS.md.
- Model the data. Decide Pydantic (boundary/validated) vs. dataclass
(internal/domain); make domain models immutable where possible.
- Define the interface first. Sketch the public function/class signatures
and any
Protocol for external dependencies.
- Implement the core pure logic, keeping I/O behind injected adapters.
- Wire dependencies explicitly (constructor/params), not via globals.
- Type and lint: run
make format then make lint (ruff + mypy).
- 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
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.