I've been using AI agents and autocomplete tools on a greenfield FastAPI project. The initial development was extremely fast, but as the codebase grew to include multiple routers, complex Pydantic schemas, and SQLAlchemy models, the structural debt started to accumulate.
The generated code usually works, but it often ignores the intended architecture—for example, placing business logic in route handlers instead of services or mishandling async database sessions across modules. I'm spending so much time restructuring generated code that writing some of it manually would have been faster.
Has anyone else experienced AI becoming less useful as a project grows? What practices, documentation, tooling, or workflow changes help you keep the architecture clean?
5 Answers
The strongest solution is to make the architecture enforceable rather than relying only on instructions. For example, hide SQLAlchemy session access behind repositories or a dedicated data-access layer, and don’t allow router modules to import sessions directly. Put API schemas and domain models in separate packages so internal models can’t accidentally become public contracts.
The fewer valid paths the codebase exposes, the fewer architectural decisions the model can get wrong. Directory boundaries, dependency rules, type checking, and small focused modules are more dependable than repeating a long prompt.
Add automated checks for the rules you care about. Tools such as import-linting, AST-based checks, dependency analysis, formatting, type checking, and tests can catch violations in CI. You can also write tests that verify important boundaries, such as preventing a route from importing a repository implementation or a domain layer from depending on FastAPI.
A scheduled architecture review is useful too: have an AI tool scan the repository for boundary violations and propose refactors, but treat the result as a review report rather than accepting all changes automatically.
A lot of the problem is that the model has strong local context but a weak global view. It sees the nearest similar function and tends to repeat that pattern, even if it violates a boundary elsewhere. Keeping persistent project guidance—such as architecture, backend, database, and frontend documents—and linking those from the main agent instructions can reduce this drift.
Still, the output is nondeterministic, so review remains necessary. If a session starts making increasingly strange changes, starting a fresh one with a clean, focused context is usually more effective than arguing with it.
Treat the AI like a junior developer that needs explicit constraints and review. Keep an architecture document describing the layers, naming conventions, async session rules, dependency flow, and examples of correctly implemented features. Have the tool read it before planning or changing code, and ask it to audit the relevant area before making edits.
Keep requests narrow, ask for a plan first, and have it stop if it discovers a design decision that wasn’t covered. It’s also often more reliable to use AI for small changes, refactoring, debugging, and discussion rather than committing entire generated sections unchanged.
That makes sense. The biggest improvement seems to come from forcing it to inspect an existing implementation and copy the established structure instead of asking for a feature in isolation.
The safest workflow is to keep as many design decisions as possible with the human. Specify the target files, existing pattern to follow, function and class names, data structures, session lifetime, and tests to update. Then let the AI implement a small, well-defined piece.
Before accepting anything, read it as production code. AI-generated code can be functionally correct while still being difficult to maintain, and that tradeoff becomes expensive once the project is large.

This is especially helpful in a hexagonal-style design. Static import rules catch problems that are easy to miss during a normal feature review, even when the generated code passes its functional tests.