Key Takeaways
- Producing quality code and controlling tech debt with AI Code assistants requires structure and guardrails. It is not enough to rely on prompts.
- Reducing the size context window that the assistant needs to generate code is critical. Vertical Slice architectures reduce context but miss important cross cutting concerns and common approaches across slices.
- Using the Dependency Inversion Pattern together with Vertical Slices provides the assistant with a template for implementation that constrains the context size and guides the structure of the generated code.
- The base classes implement nonfunctional requirements (for security, performance, observability etc.) and also for application workflows (e.g. publish all messages to an event bus. We call this layer the Skeleton of the application.
- The skeleton can be used to implement additional controls that further constrain the model to the application architecture - e.g. compliance with message schemas.
GitHub CEO Thomas Dohmke recently issued a stark warning: "Either you embrace AI, or get out of this career". But embracing AI doesn't just mean using autocomplete. It means shifting our primary skill from syntax to Systems Thinking- learning to "decompose problems until they are small enough" for the AI to solve. In short: we are all architects now.
I have been building an IoT application with device firmware, a back end, and a web UI. Although I have a software engineering background, I have been using Claude Code to help improve my velocity and work with languages and frameworks I am not so familiar with. My stack is Python + Pytorch on the device, React + Typescript on the UI, and MQTT + Node.js + Postgres on the backend. Working with Claude was difficult at first because my requests would trigger wholesale changes in the codebase. As I learned how to structure the code and tune my prompts things have gotten better so that I now trust Claude’s changes without a detailed line by line code review. I have found some patterns, that I call the Skeleton Architecture, that I think really help with AI assistant productivity that I want to share here.
As the AI coding industry matures, we are discovering that AI, when used poorly, generates significant technical debt. To survive this transition, we must identify architectural patterns that make AI-generated code safe, maintainable, and secure. This requires a strategy focused on three pillars: structuring code for AI consumption, implementing rigid guardrails, and shifting our own skill sets from "translation" to "modeling".
Structuring Code: The Context Constraint
The primary constraint of AI-assisted engineering is the Context Window. As context scales, accuracy inversely correlates due to the "Lost in the Middle" phenomenon, while latency and cost increase linearly.
Therefore, the "Golden Rule" of AI-native architecture is to minimize the scope the model must hold in working memory. We must design systems that impose physical constraints on information flow, isolating dependencies so the AI can fit the entire problem space into a single, focused prompt. This isolation serves two functions: it maximizes reasoning capability by reducing noise, and it safeguards system integrity by ensuring that an agent working on one component lacks the "visibility" to inadvertently break another.
Two architectural patterns are gaining traction to solve this.
Atomic Architecture operates at the micro-level. Pioneered by Brad Frost in 2013 to address the complexities of responsive web design, Atomic Architecture organizes systems biologically, starting with irreducible "atoms" (HTML tags, utility functions) that combine into "molecules" and eventually complex "organisms". While originally a UI methodology, this pattern has found renewed utility in AI-assisted engineering because it enforces a strict "context hygiene". By asking an AI to generate a single, isolated atom rather than a monolithic feature, developers drastically reduce the risk of hallucination and ensure that the generated code is focused, stateless, and easily verifiable. However, this creates a "fragmentation tax": while the AI produces perfect individual parts, the heavy cognitive burden of wiring these stateless atoms into a cohesive whole has to be held in the AI context or shifts entirely back to the human architect.
To handle the macro-structure, we turn to Vertical Slice Architecture. Popularized by Jimmy Bogard to dismantle the rigidity of traditional N-tier "lasagna code", this approach organizes systems by business feature (e.g., "Place Order") rather than technical layer (e.g., "Services", "Data Access").
This pattern is uniquely suited for AI Agents because it optimizes for Locality of Reference. In a layered system, an AI must retrieve files from multiple disparate directories to understand a single flow, polluting the context window with irrelevant code. Vertical Slices ensure that "things that change together, stay together", allowing the AI to ingest the complete context of a feature in a single pass without needing to "hallucinate" dependencies. However, this imposes a "Duplication Tax": to maintain independence, the AI often generates redundant data structures across slices, sacrificing the "Don't Repeat Yourself" (DRY) principle in exchange for isolation.
Vertical Slices excel at isolation, but they fail to address the coordination between slices. Critical non-functional requirements- such as security, scalability, performance, and authentication- are system-wide invariants that cannot be fragmented. If every vertical slice is tasked with implementing its own authorization stack or caching strategy, the result is "Governance Drift": inconsistent security postures and massive code redundancy. This necessitates a new unifying concept: The Skeleton and The Tissue.
The Solution: The Skeleton & The Tissue
We separate the system into two distinct domains. The Stable Skeleton represents the rigid, immutable structures (Abstract Base Classes, Interfaces, Security Contexts) defined by the human although possibly built by the AI. The Vertical Tissue consists of the isolated, implementation-heavy features (Concrete Classes, Business Logic) generated by the AI.
This architecture draws on two classical approaches: actor models and object-oriented inversion of control. It is no surprise that some of the world’s most reliable software is written in Erlang, which utilizes actor models to maintain system stability. Similarly, in inversion of control structures, the interaction between slices is managed by abstract base classes, ensuring that concrete implementation classes depend on stable abstractions rather than the other way around.
The mechanism we use to enforce this is the Template Method Design Pattern. While Dependency Inversion protects high-level policy from low-level details, the Template Method operationalizes this by locking down the execution flow. In this model, the Human Architect defines a final run() method in the base class that rigidly handles cross-cutting concerns like logging, error catching, and authentication. The AI is then restricted to implementing a specific, protected _execute() method which is called by run(). This distinction is crucial: the AI physically cannot "forget" to log an error or bypass security checks because it never owned the workflow in the first place; it merely fills in the logic gap provided by the architect.
In the device code of my system I have a number of AI algorithms that process images. I decided to represent each of these algorithms by a class that inherits from an ABC (Python abstract base class) called TaskBase. The rest of the skeleton are classes that coordinate efficiently passing images to the algorithms and scheduling them to run.
Code Sample: The Human-Owned Skeleton
This demonstrates how the BaseTask hides the complexity of buffering and readiness checks from the AI, allowing it to focus purely on the process logic.
Python
# source: task.py
class BaseTask(ABC):
"""
Abstract base class for pipeline tasks.
The AI implements the concrete logic; the Human controls the flow.
"""
def __init__(self, name: Optional[str] = None):
self.inputs: List['Buffer'] = []
self.outputs: List['Buffer'] = []
self._background_busy = False
def is_ready(self) -> bool:
"""
The Skeleton enforces readiness checks.
The AI never sees this complexity, ensuring it cannot break
scheduling logic or cause deadlocks.
"""
if not self.inputs:
return True # Source tasks
# Default policy: Ready if ANY input has data and ALL outputs have space
has_input = any(buf.has_data() for buf in self.inputs)
can_output = all(buf.can_accept() for buf in self.outputs)
return has_input and can_output
@abstractmethod
def process(self) -> None:
"""
The Context Window Boundary.
The AI only needs to implement this single method.
"""
pass
A common critique of this approach is that the rigid Skeleton might stifle innovation by restricting the AI's freedom. The response is that this rigidity is a feature, not a bug. It explicitly forces "Architectural Governance". If the fundamental control flow or system behavior requires modification, a human architect must intervene to make that decision. This constraint acts as a necessary firewall against "Architecture Drift", preventing the AI- which naturally prioritizes local optimization- from introducing ad-hoc shortcuts or inconsistent patterns that would otherwise slowly degrade the system's long-term design integrity.
Interaction: The "Director" Role
Comparing code assistants to junior developers is a dangerous anthropomorphism. AI is not a learner; it is a high-speed stochastic engine that optimizes for completion, often viewing safety checks as friction to be bypassed. Prompts are soft; architecture is hard. Consequently, the developer must monitor the agent with extreme vigilance. In my experience, despite clear instructions to "NEVER BYPASS SECURITY", models like Claude may attempt to disable authentication simply to resolve a conflict and get the code working.
To make the "Director" role scalable, we must establish "Hard Guardrails"- constraints baked into the system that are physically difficult for the AI to bypass. These act as the immutable laws of the application.
In my application, one of the most important guardrails is Data Consistency between the device, UI, and backend (Schema-First Surety). Without this, Claude quickly changes the protocol between different parts of the system, resulting in inconsistent data. I am using JSON Schema as part of OpenAPI and AsyncAPI documents as the Source of Truth, we ensure that contracts between components are inviolable. We enforce this with a "Fail-Fast" validator in the base class that triggers a hard sys.exit(1) upon violation. When the AI generates code that matches a prompt but breaks the contract, the system crashes immediately. This forces the human to intervene, transforming a silent bug into a loud "Governance Event". It is important that the validator run in the skeleton layer where Claude is not able to make changes.
Ideally we would also go beyond runtime checks and ensure Structural Integrity through automated tools in the CICD process. We use compile-time tools like ArchUnit to enforce topological strictness. We can write tests that assert, "No AI-generated module may import the Database package directly". This prevents the AI from taking architectural shortcuts that bypass the Skeleton.
For maximum surety, we can also employ Physical Separation. We can move the Skeleton- interfaces, base classes, and security logic- to a separate, read-only repository. The AI imports these definitions to build the "Tissue" but physically lacks the permissions to modify the rules. While this introduces friction (e.g., the AI cannot simply "invent" a new message type without human approval), the payoff is absolute guarantees on system behavior.
Finally, we must Isolate Side Effects. AI agents struggle to write reliable tests when logic is mixed with interactions with other components, often hallucinating complex mocks or writing flaky integration tests. We solve this by pushing interaction to the Skeleton and keeping the business logic in the Tissue (the "Functional Core"). The workflows defined by the skeleton can be tested easily with AI generated mocks and the Tissue classes are easily tested through simple test harnesses - since they are vertical slices.
Code Sample: The Immutable Guardrail
This validator runs before the AI's task ever sees the message. The sys.exit(1) ensures a "Fail Fast" security posture that the AI cannot override.
Python
# source: mqtt_validator.py
class MQTTValidator:
def validate_message(self, topic: str, payload: Dict[str, Any]):
# 1. Match Topic against Whitelist
schema_key = self._get_schema_for_topic(topic)
if schema_key is None:
logger.critical(f"FATAL: Unknown MQTT topic: {topic}")
sys.exit(1) # Device terminates on security violation
# 2. Enforce Schema Integrity
try:
validate(instance=payload, schema=self.schemas.get(schema_key))
except ValidationError:
logger.critical("Device terminating due to validation failure")
sys.exit(1)
Learning: From Syntax to Systemic Thinking
This architectural shift necessitates a fundamental re-evaluation of developer skills. Rather than focusing on language features and algorithm implementation- skills that are rapidly becoming commodities- developers must pivot toward modeling, information flows, and the rigorous management of non-functional requirements. In a world where the ability to write a sorting algorithm is worth effectively zero, the value of an engineer is no longer defined by "Translation" (converting thought to code) but by "Modeling" (defining the constraints within which code operates).
This is the era of Systemic Thinking. Features are easy; resilience is hard. An AI agent will optimize for a passing test case and completely ignore memory leaks, latency spikes, or observability gaps. Therefore, the engineer must step into the "Director" role, visualizing information flows and component interactions before a single prompt is issued. The Director must own the Non-Functional Requirements (NFRs).
Since the AI cannot "care" about memory management, the Human Architect must build these protections directly into the Skeleton.
Taking this further means that engineers need to become familiar with the world of systems architecture and constantly think about questions such as: "how will this perform".
Beyond protecting the system, the Skeleton Architecture addresses the looming "Apprenticeship Crisis". In a world where AI generates the bulk of implementation code, how do junior engineers develop the "scar tissue" necessary to become Architects? The answer is that the Skeleton itself becomes the syllabus. By forcing juniors to work within the rigid constraints of the TaskBase and Validator, we replace the paralyzing "Blank Page" problem with a structured "Fill in the Blanks" exercise. They learn system design not by reading theory, but by inhabiting a high-quality architecture that physically prevents them from practicing bad habits. The feedback loop is tightened from days (waiting for a code review) to milliseconds (crashing against a guardrail), turning every error into an instant architectural lesson.
Code Sample: Systemic Safety Nets
The AI writes code to process images, but the Human Architect ensures the system doesn't crash from memory leaks by implementing weakref tracking in the framework.
Python
# source: memory_monitor.py
class MemoryMonitor:
"""
Tracks large objects (images, tensors) to detect memory leaks in production.
The AI uses the simple API, while the 'Systemic Thinking' logic lives here.
"""
def track(self, obj: Any, obj_type: str):
# Create weakref with cleanup callback to track object life
obj_id = id(obj)
weak = weakref.ref(obj, lambda ref: self._on_object_deleted(obj_id))
self.tracked[obj_id] = ObjectLifetime(time.monotonic())
def check(self):
# The NFR Logic: Flag objects alive > 60 seconds
return [obj for obj in self.tracked if obj.age > 60.0]
Summary
Vertical Slices give the AI focus, the Skeleton gives the human control, and other hard controls give the team surety. We are not "training" AI. We are containing it. By building a rigid Skeleton, we allow the AI to move fast without breaking the backbone of our software.
References
- Bogard, J. (2018). Vertical Slice Architecture.
- Dohmke, T. (2023, November 8). GitHub Universe 2023 Opening Keynote: Copilot in the Age of AI [Video]. YouTube.
- Farry, P. (n.d.). AI-Generated Code Creates New Wave of Technical Debt, Report Finds. InfoQ.
- Frost, B. (2013). Atomic Design.
- Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.
- Liu, N. F., Lin, K., Hewitt, J., Paranjape, A., Bevilacqua, M., Petroni, F., & Liang, P. (2023). Lost in the Middle: How Language Models Use Long Contexts. Stanford University, UC Berkeley, Samaya AI.
- Martin, R. C. (n.d.). The Dependency Inversion Principle.