Introduction
In Roman mythology, Janus was the god of gates, doors, thresholds, beginnings, and transitions. January is named after him. He is usually represented with two faces. One face looks backward: to the learned path, previous projects, repeated mistakes, reusable patterns, architectural conventions, and the lessons learned from codebases that became hard to maintain. The other face looks forward: to future frameworks, new channels, changing model providers, different storage layers, and capabilities that do not exist yet.
This double vision is increasingly important when building software with AI coding assistants. With modern coding agents, it is now possible to create a working prototype in a few hours. Code is cheaper to generate, easier to modify, and faster to assemble. But this speed also creates a new problem: when code is produced quickly without a clear structure, the project can become difficult to understand, maintain, and extend.
For data scientists, this shift is particularly interesting. Applications that previously required substantial software engineering support can now be prototyped using natural language and AI coding agents. Moving from notebooks and experiments to APIs, interfaces, and deployed applications has become more accessible. Yet generating a working application and engineering a system that can evolve are two very different challenges.
I explored this problem in more detail in a previous article on hidden technical debt in AI development [1], where the main argument was that speed without standards can create fragile AI products. This article continues from that idea, but focuses on a more practical question: what kind of starting structure can help us build AI applications that are easier to evolve?
The risk is not only that the code is messy today. The deeper risk is that the project has no memory of past architectural lessons and no room for future change. A coding agent can add a function, create a file, wire an endpoint, or integrate a framework very quickly. But without a clear structure, it may place logic wherever it is easiest in the moment: inside an endpoint, inside a framework-specific object, inside a helper function, or inside a file that was never meant to own that responsibility.
This is why AI/ML projects need Janus-like vision. Looking backward, we should capture the lessons that teams often learn the hard way: separate responsibilities, avoid duplicated setup, keep contracts clear, test the core logic, and prevent external tools from leaking into every part of the application. Looking forward, we need enough flexibility to accommodate new requirements, technologies, and capabilities without redesigning the entire system.
The goal is not architecture for its own sake. The goal is to make fast development safer and more repeatable. A baseline defines the minimum architectural decisions that should not be reinvented in every project: tool-agnostic code, replaceable infrastructure, testable core logic, lower coupling to frameworks, and clear separation of concerns. When that baseline is packaged as a reusable template, it becomes a scaffold: a practical starting structure with configuration, backend services, frontend wiring, Docker, tests, documentation, schemas, adapters, and development rules already in place. This matters because many AI and data science applications need similar foundations. If every project recreates them from scratch, teams lose time and accumulate inconsistencies. A scaffolded baseline allows teams to move faster without turning every new application into a one-off structure.
If AI agents can write the code, what do we still need to understand to engineer software that lasts?
1. Baselines: Giving AI Projects a Starting Architecture
A baseline gives a project a clear version one. It defines what we know, what we accept as the current starting point, and which principles future iterations are expected to preserve. Without this reference, teams can keep changing an application without being able to tell whether they are improving it or simply making it different.
This becomes especially important in AI-assisted development. If there is no agreed architecture, no defined boundaries, and no established patterns, every iteration can reshape the system in a different direction. Even when teams use Cursor/Claude/Codex rules, skill files, or coding guidelines, those instructions are not a substitute for an architectural baseline.
These mechanisms remain useful because they help coding agents understand local conventions and generate code more consistently. However, they work better when there is already a structure to follow. The baseline provides both the agent and the developer with a stable reference: where responsibilities belong, which boundaries should not be crossed, and what patterns should be reused.
For data scientists, the baseline will depend on the type of application being developed. An agentic workflow may need explicit boundaries for tools, state management, permissions, and auditing. A document-processing system may separate classification, extraction, validation, and human review. A predictive service may isolate feature processing, model inference, monitoring, and fallback mechanisms.
The objective is not to impose the same architecture on every project, but to preserve reusable engineering principles while keeping applications independent of specific technologies.
2. Architecture Choices: What Data Scientists Should Know
There are many ways to organize a software system, and each architectural choice addresses a different concern. The problem begins when an application grows without clear boundaries, responsibilities, or dependency rules.
This is particularly common when moving from notebooks and experimental scripts to production applications. Model inference, preprocessing, validation, API logic, database access, business rules, and external integrations can quickly become intertwined. The application may work, but replacing a model, changing a database, or testing individual components becomes increasingly difficult.
Software engineering offers well-established principles to address these challenges [2]:
-
Separation of Concerns and SOLID principles: Help organize responsibilities, reduce coupling, and keep components focused, extensible, and interchangeable.
-
Clean Architecture and Hexagonal Architecture: Provide approaches for protecting application logic from external dependencies, such as databases, frameworks, model providers, and third-party services [3].
-
Ports, adapters, and dependency injection: Translate architectural boundaries into practical mechanisms, allowing implementations to be replaced without changing the underlying business logic.
Consider a predictive application that relies on a particular model provider, database, and API framework. If those dependencies are embedded directly into the prediction logic, changing any of them may require modifications throughout the application. By introducing clear interfaces and adapters, the core behavior can remain stable while external components evolve independently.
The same principle applies to agentic workflows. An agent should not need to know the implementation details of every tool, storage mechanism, or external service it uses. Clear contracts and dependency boundaries make those components easier to replace, test, and maintain.
Several implementations illustrate these architectural principles in practice. One particularly useful example comes from AWS Prescriptive Guidance [10], which provides a Python application built around Hexagonal Architecture. The reference project separates domain logic, ports, adapters, API entry points, tests, and infrastructure. We use this example because AWS provides both the implementation and detailed guidance, making it easier to understand how architectural decisions translate into executable code.
As illustrated in Figure 1, different architectural approaches can complement each other. A practical AI baseline may combine vertical slicing to organize related capabilities, Clean Architecture to protect core application logic, Hexagonal Architecture to connect that logic through ports and adapters, and dependency injection to keep implementations interchangeable.
The objective is not to follow a particular architecture mechanically, but to understand which responsibilities should remain stable and which components should be free to change.
3. Design Patterns as Lego Bricks: Turning Boundaries into Code
Software architecture defines the overall shape of a system: its responsibility boundaries, dependency rules, and the separation between stable application logic and changing technologies. But architectural decisions must eventually be translated into code. This is where design patterns become useful.
Think of design patterns as LEGO bricks: reusable building blocks that help implement architectural decisions without reinventing solutions for recurring problems.
For example, if we want to replace an LLM provider without rewriting the application, an Adapter can isolate the provider-specific implementation. If we want to test business logic without connecting to an actual database, a Repository interface combined with Dependency Injection can separate persistence from application behavior. If we need different model-selection strategies, a Strategy pattern can make those alternatives interchangeable.
Other patterns, such as Factory and Registry, can help create, configure, and discover components without hardcoding every implementation into the application. Figure 2 summarizes several recurring mechanisms that help translate architectural principles into concrete implementations.
These patterns are not exclusive to AI development. They are established software engineering practices that become particularly valuable when building applications with coding agents. Instead of repeatedly asking an agent to invent a new integration or component structure, we can provide familiar patterns and conventions that guide its implementation.
The objective is not to introduce as many patterns as possible. Excessive abstraction can make a simple application unnecessarily complex. The value comes from recognizing recurring problems and applying the smallest set of patterns that helps preserve clear boundaries, testability, and flexibility.
Architecture defines the boundaries. Design patterns provide the building blocks to implement them.

4. From Architectural Baselines to Scaffolds and Harnesses
Understanding architectural principles is useful, but applying them consistently across projects is a different challenge. Data scientists often develop applications with similar foundations: API endpoints, model inference services, configuration management, logging, tests, external integrations, and deployment infrastructure. Recreating these components for every new project is time-consuming and can introduce unnecessary inconsistencies.
This is where scaffolding becomes valuable.
An architectural baseline defines the principles, boundaries, and conventions a project should follow. A scaffold turns those decisions into a reusable starting structure, with predefined modules, interfaces, configuration, tests, and extension points. Instead of starting from an empty repository or asking a coding agent to invent the entire application structure, developers begin with a foundation that already incorporates established engineering practices.
Tools such as Cookiecutter[4], Copier[5], and Plop[6] can automate this process. Template engines generate initial project structures from predefined configurations, while specialized generators can add recurring components, such as API endpoints, adapters, repositories, or tests, as an application grows.
However, a scaffold is not necessarily limited to generating an application once. Around it, a generation harness can coordinate how projects are configured, instantiated, extended, and validated. This may include configuration schemas, generators, pre- and post-generation hooks, dependency setup, formatting, and automated checks.
For example, a pre-generation hook can validate project names and configuration options, while a post-generation hook can register components, initialize dependencies, format code, or execute tests. Tools such as OpenAPI Generator can complement the scaffold by generating API interfaces and client code from established contracts.
AI coding agents introduce another layer of flexibility. They can generate domain-specific implementations that cannot be fully described through fixed templates, such as a new fraud detection workflow, a document extraction component, or a model inference service.
Recent research on harness engineering provides an interesting perspective on this emerging practice. Galster et al. [9] examined configuration mechanisms for agentic coding tools across 2,853 GitHub repositories, identifying approaches such as context files, skills, and subagents. Their findings highlight how developers are beginning to formalize the instructions and reusable capabilities available to coding agents.
The important distinction is that deterministic generators provide predictable structures, while coding agents provide flexibility within those structures. Generated changes can then be evaluated using architectural dependency checks, schema validation, linting, type checking, security scanning, and automated tests.
Beyond code generation, coding agent harnesses can provide reusable instructions, skills, plugins, and external tools that help agents understand and modify existing applications. For example, tools such as CodeGraph [8] can expose codebase structure, dependencies, and change impact through MCP, giving agents structured context before making modifications. These capabilities complement scaffolding by supporting development beyond the initial project setup. The architecture and governance of coding agent harnesses deserve a deeper discussion, which I will explore in a separate article.
While generation harnesses standardize how applications are created and extended, coding agent harnesses organize the context, capabilities, and controls available to agents during development. Together, they connect reusable architectural foundations with AI-assisted implementation.
This approach does not eliminate engineering decisions or guarantee that every generated component will be correct. Instead, it reduces repetitive setup and creates a more consistent environment in which developers and coding agents can build, test, and evolve AI applications.
As illustrated in Figure 3, these mechanisms can work together throughout the development lifecycle, from defining architectural foundations and generating project structures to AI-assisted implementation, validation, and continuous improvement.

Conclusion: Engineering Matters More
AI coding agents are changing how software is built. Tasks that once required hours of implementation can now be delegated to systems capable of generating code, modifying repositories, running tests, and proposing complete solutions.
But making code easier to produce does not make software easier to engineer.
Someone still needs to decide how responsibilities should be separated, which dependencies are acceptable, what should remain replaceable, and how the system can evolve without breaking existing behavior. For data scientists moving beyond experimentation into application development, understanding these decisions becomes increasingly important.
This is where architectural baselines, reusable scaffolds, and harnesses come together. The baseline captures engineering decisions and lessons learned. The scaffold translates them into a practical starting structure. Generation harnesses automate and validate recurring development tasks, while coding agent harnesses provide the instructions, capabilities, tools, and controls that guide AI-assisted implementation.
However, these mechanisms should not replace architectural reasoning. They should make it more explicit, reusable, and easier to verify. Otherwise, we risk automating not only code generation but also the accumulation of architectural mistakes.
Returning to Janus, good engineering requires looking backward to preserve what we have learned and forward to anticipate what may change. The goal is not to generate more code, or even to generate it faster, but to build applications that remain understandable, maintainable, and adaptable as requirements, technologies, and teams evolve.
The easier it becomes to write code, the more important it becomes to engineer what we build.
References
[1] Gomes-Gonçalves, E. (2026, January 27). Do you smell that? Hidden technical debt in AI development: Why speed without standards creates fragile AI products. Towards Data Science. https://towardsdatascience.com/do-you-smell-that-hidden-technical-debt-in-ai-development/
[2] Richards, M. (2015). Software architecture patterns. O’Reilly Media. https://www.oreilly.com/library/view/software-architecture-patterns/9781491971437/
[3] Amazon Web Services. (n.d.). Hexagonal architecture pattern. AWS Prescriptive Guidance. https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/hexagonal-architecture.html
[4] Cookiecutter. (n.d.). Cookiecutter: Better project templates. https://cookiecutter.readthedocs.io/
[5] Copier. (n.d.). Copier documentation. https://copier.readthedocs.io/en/stable/
[6] Plop. (n.d.). Plop documentation. https://plopjs.com/documentation/
[7] DrivenData. (n.d.). Cookiecutter Data Science. https://cookiecutter-data-science.drivendata.org/
[8] CodeGraph. (n.d.). CodeGraph: Cross-language code intelligence for AI agents and developers. GitHub. https://github.com/codegraph-ai/CodeGraph
[9] Galster, M., Mohsenimofidi, S., Lulla, J. L., Abubakar, M. A., Treude, C., & Baltes, S. (2026). Harness engineering for agentic AI coding tools: An exploratory study. arXiv. https://arxiv.org/abs/2602.14690
[10] Oruc, F., Goby, D., Kunce, D., & Ploski, M. (n.d.). Structure a Python project in hexagonal architecture using AWS Lambda. AWS Prescriptive Guidance. https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/structure-a-python-project-in-hexagonal-architecture-using-aws-lambda.html

