Python Package Layout

Python Package Layout

There are two common layouts for structuring a Python project.


Flat Layout (typical starting point)

aiservice/
├── api/
├── services/
├── test_interface/
├── models/
├── prompts/
├── jre/
├── data/
└── pyproject.toml

Everything sits at the top level. The problem: setuptools sees all folders and can't tell which are Python code vs data. It finds api/, services/, jre/, models/, prompts/, data/, test_interface/ and panics because it doesn't know what to include.

Src Layout (the proper way)

aiservice/
├── src/
│   └── epic_aiservice/
│       ├── api/
│       ├── services/
│       └── test_interface/
├── models/
├── prompts/
├── jre/
├── data/
└── pyproject.toml

Python code lives inside src/, data lives outside. No ambiguity.


Src Layout with AI Agents

If your project includes AI agents, MCP, guardrails, or multi-agent orchestration, extend the src layout with dedicated folders for each concern:

aiservice/
├── src/
│   └── epic_aiservice/
│       ├── api/                        # REST/WebSocket endpoints
│       ├── services/                   # Business logic
│       ├── test_interface/             # Dev/test UI
│       │
│       ├── agents/                     # ── Agent definitions ──
│       │   ├── __init__.py
│       │   ├── base.py                 # Base agent class/interface
│       │   ├── research_agent.py       # One file per agent
│       │   ├── coding_agent.py
│       │   └── ...
│       │
│       ├── tools/                      # ── Tool functions ──
│       │   ├── __init__.py
│       │   ├── base.py                 # Base tool class/interface
│       │   ├── web_search.py           # One file per tool
│       │   ├── file_reader.py
│       │   └── ...
│       │
│       ├── guardrails/                 # ── Input/output validation ──
│       │   ├── __init__.py
│       │   ├── input_validators.py     # Prompt injection, PII detection, etc.
│       │   ├── output_validators.py    # Hallucination, toxicity checks, etc.
│       │   └── content_filters.py      # Blocked topics, profanity, etc.
│       │
│       ├── mcp/                        # ── Model Context Protocol ──
│       │   ├── clients/
│       │   │   ├── __init__.py
│       │   │   └── client.py           # MCP client connections
│       │   └── servers/
│       │       ├── __init__.py
│       │       ├── tools_server.py     # Expose tools via MCP
│       │       └── resources_server.py # Expose data via MCP
│       │
│       ├── orchestrator/               # ── Multi-agent coordination ──
│       │   ├── __init__.py
│       │   ├── router.py               # Routes tasks → agents
│       │   └── workflow.py             # Multi-agent workflows
│       │
│       └── config/                     # ── Configuration ──
│           ├── settings.py             # Env vars, model configs
│           ├── agent_registry.py       # Maps agent names → classes
│           └── tool_registry.py        # Maps tool names → classes
│
├── prompts/                            # ── Prompt templates (not Python) ──
│   ├── system/
│   │   ├── research_agent.md           # System prompt per agent (.md for long prose)
│   │   ├── coding_agent.md
│   │   └── ...
│   ├── templates/
│   │   ├── summarize.md                # Reusable prompt templates
│   │   └── ...
│   └── configs/
│       ├── research_agent.yaml         # Prompt configs (.yaml for structured data)
│       ├── few_shot_examples.yaml      # Few-shot examples with variables
│       └── ...
│
├── models/                             # ML model files
├── data/                               # Static data, embeddings, etc.
├── jre/                                # Java runtime (if needed)
└── pyproject.toml

When to Graduate: Flat vs Folder-per-Agent

The layout above uses a flat agents folder (one .py file per agent). This is fine for prototypes and small teams. When you need independent deployment, scaling, or dependencies per agent, switch to folder-per-agent:

Flat (prototype / small team):

agents/
├── __init__.py
├── base.py
├── research_agent.py          ← just a file
├── coding_agent.py
└── ...

Folder-per-agent (production / scaling):

agents/
├── shared/                          # ── Shared across all agents ──
│   ├── base.py                      # Base agent class/interface
│   └── utils.py
│
├── research_agent/                  # ── Each agent = its own deployable unit ──
│   ├── agent.py                     # The agent class
│   ├── lambda_handler.py            # AWS Lambda entry point
│   ├── Dockerfile                   # Container for deployment
│   ├── pyproject.toml               # Dependencies for THIS agent only
│   ├── uv.lock                      # Locked dependencies
│   ├── tests/
│   │   ├── test_agent.py
│   │   └── test_integration.py
│   └── prompts/                     # Prompts specific to this agent
│       ├── system.md
│       └── config.yaml
│
├── coding_agent/
│   ├── agent.py
│   ├── lambda_handler.py
│   ├── Dockerfile
│   ├── pyproject.toml
│   ├── uv.lock
│   ├── tests/
│   │   └── test_agent.py
│   └── prompts/
│       ├── system.md
│       └── config.yaml
│
└── ...
Flat Folder-per-agent
Deploy All agents deploy together Each agent deploys independently
Dependencies Shared pyproject.toml Each agent has its own
Scaling Scale everything or nothing Scale individual agents
Team One person / small team Different people own different agents
Testing Run all tests at once Test each agent in isolation
Use when Prototype, MVP, < 3 agents Production, > 3 agents, different infra needs

Note: In the folder-per-agent layout, agent-specific prompts live inside the agent folder. Shared prompts (used by multiple agents) still live in the top-level prompts/ folder.

This pattern works for any platform — web apps, desktop apps, mobile. The agents always run server-side (Lambda, Cloud Run, ECS, etc.) and the frontend calls them via API. The frontend technology is irrelevant to how you structure your agents.


Why This Structure

Folder What goes here Why separate
agents/ One class per agent with its system prompt, tools list, and loop Each agent is independently testable
tools/ Pure functions that agents call (API wrappers, DB queries, etc.) Tools are shared across agents
guardrails/ Input/output validators attached to agents Security/safety is a cross-cutting concern
mcp/clients/ Code that connects to external MCP servers Clients consume tools from other services
mcp/servers/ Code that exposes your tools/resources via MCP Servers share your tools with other apps
orchestrator/ Router that picks the right agent, workflow pipelines Multi-agent coordination lives here
config/ Registries that map names → classes, env vars, model selection Avoids hardcoded imports everywhere
prompts/ Markdown files for system prompts and templates Outside src/ — not Python code, just text data

Key rule: prompts/ stays outside src/ (like models/ and data/) because prompts are text data, not Python packages. Everything inside src/ is importable Python code.

.md vs .yaml for prompts:

  • .md — best for long system prompts (readable prose, supports headings/formatting)
  • .yaml — best for structured prompts with variables, few-shot examples, model params (role, content, temperature, etc.)