AI writing code has one very common problem:

It knows how a feature should be implemented, but doesn’t necessarily know where the code should go.

For example, if you tell AI:

“Add a delete Agent feature.”

Without a clear project structure, it might directly, inside the API endpoint: query the database, evaluate business rules, clear the cache

The feature might work quickly. But as AI writes code this way again and again, the project will eventually become:

Business logic inside the API
Database operations inside Services
Business judgments inside Repositories
Utility classes referenced everywhere

At this point, we need another very fundamental yet extremely important architectural concept:

Layering.


📌 Technical Profile

Layered Architecture

Divide the system into several layers based on different responsibilities — such as the interface layer, business layer, data access layer, and infrastructure layer — and define what each layer is responsible for and which layers it can call.

While MVC and Clean Architecture differ in specific form, they all share a common idea:

Don’t mix different types of code together.

💡 One-Sentence Understanding

Think of a software system as a restaurant.

Customers don’t run directly into the storeroom to grab ingredients, and waiters don’t run into the kitchen to cook.

It usually goes:

Customer
Waiter
Chef
Storeroom

Each layer has its own job.

Software is the same:

User request
API / Controller
Service
Repository
Database

The most important thing isn’t “creating a few more folders.”

It’s:

Who handles requests, who handles business, who handles the database — these must be clearly defined upfront.

For AI, this is like drawing clear floors in a building:

You can work on your own floor, but don’t casually walk through walls.


I. Why Does AI Especially Need “Layering”?

When a human developer sees a piece of code, they often judge from experience:

“This SQL shouldn’t be in the Controller.”

AI, without explicit rules, might think:

“Writing it here is fastest, and it gets the task done.”

It’s more concerned with whether the current problem is solved than whether the entire project will become messy six months later.

So if a project has no clear layering, AI easily gradually writes:

API
├── Parameter validation
├── Permissions
├── Business logic
├── SQL
├── Caching
└── Third-party APIs

Eventually a single endpoint file is hundreds or even thousands of lines long. What layered architecture truly solves is:

First define where code should live.


II. Architecture Standards: Give Each Layer Clear Responsibilities

An easy-to-understand web backend can be simplified into these layers:

API / Controller
Service
Repository
Database

More complex systems can also add:

Schema
Runtime
Infrastructure

But the principle remains unchanged.


1. API Layer: Responsible for “Reception”

The API layer is mainly responsible for:

Receiving requests
Parameter transformation
Identity / permission entry point
Calling Service
Returning results

It should not be responsible for core business logic.

For example:

@router.delete("/{agent_id}")
async def delete_agent(
    agent_id: int,
    svc: AgentService = Depends(get_service),
):
    await svc.delete_agent(agent_id)
    return ApiResponse()

From a readability perspective, this is very simple:

Receive request
Call AgentService
Return result

This is a healthy API layer.


2. Service Layer: Responsible for “Business”

The Service answers the question:

How should this be done?

For example, deleting an Agent might be more than just:

DELETE FROM agents

It might also include:

Check if the Agent exists
Delete data
Clean up relationships
Invalidate cache
Record business result

These all belong to the business process.

Therefore, they should be concentrated in the Service, not scattered across the API.


3. Repository / Data Access Layer: Responsible for “How to Get Data”

The Repository is responsible for:

Querying
Inserting
Updating
Deleting
Transactions
Database access

It’s more concerned with:

How data is stored and queried.

Than with:

Why this business operation should be done this way.

For example:

Service:
After deleting Agent, clear the cache

Repository:
Execute the Agent deletion operation

The two have different responsibilities.


4. Schema / DTO: Responsible for What Data Looks Like

For example:

AgentCreate
AgentUpdate
AgentOut

They are responsible for describing:

Input parameters
Output data
Field types
Validation structures

This prevents various dict objects from freely roaming through the project.


5. Runtime: Responsible for Complex Execution Capabilities

For ordinary CRUD management features, this layer may not need to exist independently. But systems like AI Agent platforms, RAG, and workflow engines typically have:

AgentRunner
Retrieval Pipeline
LLM Runtime
Conversation Runtime
Tool Execution

These are neither ordinary database CRUD operations nor simple APIs, so they can independently form a Runtime layer.


6. Infrastructure: Responsible for Technical Infrastructure

Infrastructure typically includes:

Database connections
Caching
Logging
Configuration
Event bus
External service connections
File storage

These are technical capabilities the system needs to run. Business code should use them.


III. What Truly Matters: Calls Must Have Direction

Simply creating:

api/
services/
repositories/

A few directories isn’t enough. More importantly, you must define:

Which layers can call which layers.

The easiest rule to understand is:

API
Service
Repository
Database

And not become:

API ───────→ Database
 ↑             ↓
Repository ← Service

Otherwise, even though the directories are layered, the code is still a mess.

So we can give AI several very direct rules.

Rule 1: API Must Not Directly Access the Database

Wrong:

@router.delete("/{id}")
async def delete(id: int):
    await db.execute(...)

Recommended:

await service.delete(id)

Rule 2: API Must Not Carry Core Business Logic

Don’t write:

if agent.is_active:
    ...
if has_tools:
    ...
if user.role:
    ...
await db...
cache.clear()

These should go into the Service.


Rule 3: Repository Must Not Make Business Decisions

The Repository can:

Query Agent
Delete Agent
Update Agent

But try not to decide inside it:

“Admins can’t delete the default Agent.”

This is a business rule and belongs better in the Service.


Rule 4: Upper Layers Call Lower Layers Through Stable Interfaces

For example:

API
AgentService.delete_agent()

The API doesn’t need to know what exactly goes on inside the Service:

How many Repositories are called
Whether cache is cleared
Whether events are published

This way, when the Service’s internals change, the API can remain stable.


Rule 5: Cross-Layer Calls for Convenience Are Prohibited

This is an especially important rule for AI.

For example, if AI discovers in the API:

container.agent_db

It might call it directly inside the Router.

Just because it’s “accessible” doesn’t mean it “should be used.”

Architecture rules should be explicit:

Accessible does not equal allowed.


IV. What Are the Benefits of Doing This?

The benefits of layering aren’t just that the code looks prettier. For AI programming, it directly affects long-term code quality.


1. AI Can More Easily Determine Where Files Should Go

When AI needs to add:

An Agent query feature

It can determine:

HTTP interface
 → api

Business rules
 → services

Database queries
 → repositories

Input/output models
 → schemas

No need to redesign the project structure every time.


2. Smaller Modification Scope

If only modifying:

Agent business rules

Usually focus on checking:

AgentService

Without having to overturn the API, database, and frontend entirely.

This makes it easier for AI to execute “small-scope modifications.”


3. Easier to Test

Once the Service doesn’t depend on HTTP, it can be tested independently. The Repository can be tested independently against the database.

The API can test:

Are routes correct?
Are permissions correct?
Are inputs/outputs correct?

The testing targets become very clear.


4. Smaller Impact When Swapping Technology Implementations

For example, later:

SQLite
PostgreSQL

Ideally, the main modification is in:

Repository / Infrastructure

Rather than rewriting the business code alongside it.

Similarly, if:

FastAPI

is replaced with a different web framework in the future, the core business layer shouldn’t need to be entirely rewritten either.


5. AI Can More Easily Understand Existing Projects

For AI, the directory itself is a form of information.

Seeing:

api/
services/
repositories/
schemas/
runtime/
infra/

It can immediately infer:

This is a system with clear responsibility layering.

Much easier to understand than all Python files piled into:

app/

6. Multiple AIs Can More Easily Stay Consistent

Use one model today, switch to another tomorrow, and perhaps use a Coding Agent to auto-modify code the day after.

As long as architecture rules are stable:

API is API
Service is Service
Repository is Repository

Different AIs’ coding styles may differ, but the overall project structure won’t easily drift.


V. Prompt Implementation: Write Layering Rules for AI

Just telling AI:

“Use Clean Architecture.”

Is still too abstract.

What’s truly useful is clear responsibilities and call direction. For example, you can place the following rules in your project rules:

## Backend Layering Rules

The backend follows a strict layered architecture.

Layers:

- app/api:
HTTP routing, request parsing, authorization entry point, dependency injection, response transformation.

- app/services:
Business logic and use case orchestration.

- app/repositories:
Database access and persistence operations.

- app/schemas:
Request/response DTOs and validation models.

- app/runtime:
Agent, conversation, LLM, retrieval, and other long-running runtime capabilities.

- app/infra:
Database, cache, logging, configuration, storage, and infrastructure integration.

Rules:

- API routes must not directly access the database.
- API routes must delegate business operations to services.
- Services must not depend on FastAPI requests or HTTP details.
- Repository code should focus on persistence, not business rules.
- Do not bypass services just because repository or database objects are directly accessible.
- Prefer reusing existing services and repositories before creating new ones.
- Keep dependencies flowing within the established architecture.
- Before writing code, identify the correct layer for each responsibility.

Then the task prompt can be written like this:

Add a delete feature for Agent.

Strictly follow the project's existing layered architecture:

API only handles routing, permissions, and responses;
Business logic goes in AgentService;
Database operations reuse the existing data access layer;
Do not directly access the database or handle caching in the Router.

Before implementing, first check the existing Agent API, Service, Schema, and data access code.

Now AI gets a very clear construction route:

First find the API
Then find the Service
When data is needed, find the Repository
When infrastructure is needed, go through existing capabilities

Rather than:

“Whatever object I can reach, I’ll call.”


VI. Positive Output: What Does miniagent’s Actual Layering Look Like?

Take the actual project miniagent as an example.

miniagent’s current backend isn’t a traditional three-tier MVC (Model, View, Controller) architecture, but rather uses the following layered architecture to address the complexity of an Agent system:

flowchart TD UI["Frontend Applications
Management / Workplace"] API["Interface Layer — API Layer
app/api
Routing · Parameter Parsing · Permission Entry · Response Transformation"] SERVICE["Business Service Layer
app/services
Business Logic · Use Case Orchestration · Cross-Module Coordination"] RUNTIME["Runtime Layer
app/runtime
AgentRunner · Conversation · LLM · Retrieval · Tool"] REPO["Data Access Layer — Repository Layer
app/repositories
Query · Insert · Update · Delete · Persistence"] SCHEMA["Data Models — Schema / DTO
app/schemas
Request Models · Response Models · Data Validation"] CORE["Core Capabilities
app/core
Configuration · Security · DI · i18n · Logging"] INFRA["Infrastructure Layer
app/infra
ORM · Database Initialization · Cache · Storage"] DATA["Data & External Resources
SQLite · DuckDB · ChromaDB · BM25 · Files · LLM APIs"] UI -->|"REST / SSE"| API API --> SERVICE API -.-> SCHEMA API -.-> CORE SERVICE --> RUNTIME SERVICE --> REPO SERVICE -.-> SCHEMA SERVICE -.-> CORE RUNTIME --> REPO RUNTIME -.-> CORE RUNTIME --> INFRA REPO --> INFRA INFRA --> DATA CORE -.-> INFRA

It can be simplified to:

flowchart TD API["Interface Layer
app/api
HTTP Routing · Request / Response Handling"] SERVICE["Business Service Layer
app/services
Business Logic · Use Case Orchestration"] RUNTIME["Runtime / Data Access Layer
app/runtime · app/repositories
Agent Runtime · Data Access"] INFRA["Infrastructure / Data Layer
app/infra
SQLite · DuckDB · ChromaDB · File Storage"] API --> SERVICE SERVICE --> RUNTIME RUNTIME --> INFRA

This isn’t about making directories look pretty — it’s about telling developers and AI:

Which layer different code should work in.


1. The API Layer Explicitly Declares: Only HTTP Is Handled Here

miniagent’s current Agent API file:

backend/app/api/admin/agent.py

The file begins with:

# Agent API Router – HTTP layer only,
# all logic lives in AgentService

This sentence itself is actually excellent AI architecture prompting:

Only HTTP is handled here. All business logic goes into AgentService.

For example, deleting an Agent:

@router.delete(
    "/{agent_id}",
    response_model=ApiResponse,
    summary="Delete agent  [agent:delete]"
)
async def delete_agent(
    agent_id: int,
    svc: AgentService = Depends(get_service),
    caller_id: int = Depends(_delete),
):
    await svc.delete_agent(agent_id)
    return ApiResponse()

You can see the Router does very little:

Receive agent_id
Permission check
Obtain AgentService
Call delete_agent()
Return ApiResponse

It doesn’t do the following on its own:

Manipulate Agent database
Clean up cache
Implement Agent business rules

This is a textbook example of “keeping the API layer in check.”


2. Business Logic Goes into AgentService

Correspondingly:

backend/app/services/admin/agent.py

The file also explicitly states at the beginning:

# Agent Service – business logic layer
# (no HTTP / FastAPI imports)

And AgentService’s description:

class AgentService:
    """
    Encapsulates all business logic for the Agent resource.
    """

That is:

The Service layer explicitly does not depend on HTTP / FastAPI, and is responsible for Agent business logic.

This is a very important boundary. If AgentService is later used in:

HTTP API
Background tasks
Scripts
Tests
Other internal services

It doesn’t need to know:

Whether the current request is coming from FastAPI.


3. The Business Action of Deleting an Agent Also Happens in the Service

For example:

async def delete_agent(
    self,
    agent_id: int
) -> None:

    await self._agent_db.delete_agent(agent_id)

    self._cache.on_agent_changed(agent_id)

Here you can see the distinction in responsibilities.

The API layer only knows:

I want to delete an Agent

The Service knows:

Delete the Agent
+
Invalidate relevant caches after the Agent changes

If later we add:

Record audit log
Publish event
Clean up associated resources

These business actions can still be orchestrated by the Service, without the API becoming increasingly bloated.


4. The Service Then Calls Data Access Capabilities

When AgentService initializes, it obtains:

self._agent_db = container.agent_db

self._user_agent_relation_db =
    container.user_agent_relation_db

self._agent_tool_relation_db =
    container.agent_tool_relation_db

self._tool_db = container.tool_db

self._cache =
    container.object_cache_invalidator

Thus forming a very clear call chain:

Agent API
AgentService
Agent DB / Relation DB
Database

Meanwhile, caching is also handled through existing infrastructure capabilities:

AgentService
Object Cache Invalidator

Rather than the API doing:

redis.delete(...)

on its own.


5. Complex Capabilities Enter Runtime Separately

miniagent differs from ordinary management systems in another way:

It genuinely needs to run:

Agent
LLM
RAG Retrieval
Conversation
Tool
SQL Agent

Therefore, the project places these long-running or execution-oriented capabilities separately in:

app/runtime/

Runtime contains Agent, Session, LLM, Retrieval, and other runtime components.

This way, things like:

AgentRunner
RetrievalPipeline
LLM Client

aren’t forcibly crammed into ordinary Services.

This is also an important layering concept:

Architecture should serve business complexity, not mechanically follow templates.


VII. What’s the Difference Between Layering and the Previous Article on DDD?

These two concepts are very easy to mix up. They can be distinguished by two questions. DDD mainly answers:

Whose business is this?

For example:

Agent
Knowledge Base
Tool
Conversation
User

These are business boundaries.

Layered architecture mainly answers:

Which layer does this code belong to?

For example:

API
Service
Repository
Runtime
Infrastructure

These are technical responsibility boundaries.

A simple way to understand it:

DDD
Solves horizontal boundaries

Layered Architecture
Solves vertical boundaries

When the two are combined, AI gets an even clearer map:

First determine:
Which domain is this?

Then determine:
Which layer of code is this?

Conclusion

Layered architecture may look like just a few directories:

api/
services/
repositories/

But its true meaning goes far beyond that. It’s constantly telling AI:

Don’t write business logic where requests are received;

Don’t care about HTTP where business logic is written;

Go through the data access layer when you need data;

Reuse unified capabilities when you need infrastructure.

The stronger AI’s coding ability and the more files it can modify at once, the more important these boundaries become. Otherwise, one wrong “convenient call” could quickly be replicated by AI to dozens of places.

Therefore:

Standardized directory structure is just the surface. What truly matters is standardized responsibilities and dependency direction.

For AI programming, it can be summarized in one very simple sentence:

DDD tells AI “whose responsibility this is.” Layered architecture tells AI “which layer this should be done in.”

When the two are combined, AI truly has an engineering map it can follow for long-term construction.

Open Source Code


🪐 Good luck 🪐