AI Agents Don’t Fix Bad Architecture. They Accelerate It.
Across my previous articles, I’ve kept coming back to one central argument: Once UI rendering no longer owns the entire data flow, State, Derived State, Effects, and Async Work can no longer be carelessly compressed into the lifecycle of a single component. At first, this might sound like a frontend

Across my previous articles, I’ve kept coming back to one central argument: Once UI rendering no longer owns the entire data flow, State, Derived State, Effects, and Async Work can no longer be carelessly compressed into the lifecycle of a single component. At first, this might sound like a frontend architecture problem: Components accumulating too much local state Effects becoming overloaded with unrelated side effects Async work calling setState when it completes, tying its lifecycle directly to the UI Queries, Mutations, and Streams being treated as interchangeable operations despite having different semantics But as I began using AI agents more extensively to implement, refactor, and validate systems, I arrived at a broader conclusion: Whether an agent understands the semantic boundaries of a system matters far more than how much code it can generate. Modern AI agents can generate code at remarkable speed. They can create directory structures, complete TypeScript types, write tests, generate reducers, hooks, adapters, and API clients, and even connect an entire feature through a seemingly coherent implementation. On the surface, this looks very close to automated software development. The danger, however, is usually hidden inside code that works while leaving its semantic boundaries ambiguous. The code works on the first run, yet nobody can clearly answer a fundamental question: Who actually owns the data? Once ownership becomes unclear, several architectural questions turn into invisible time bombs: Who is responsible for maintaining state consistency? Who is allowed to trigger invalidation? Which value is the source of truth, and which is merely a derived result? When async behavior is interrupted, should it be cancelled, retried, or discarded? Is the UI a continuously subscribed observer, or is it only consuming a snapshot from a particular point in time? When these boundaries are not explicitly defined, a more productive agent does not create a better system. It simply builds an unmaintainable one faster. If developers themselves do not have a clear understanding of the system’s architectural boundaries, an AI agent will not automatically invent those boundaries for them. Instead, it will usually follow the most direct and intuitive implementation path: Data arrives → Put it into component state The data needs to be shared → Move it into a global store Something must stay synchronized → Add an effect Duplicate requests must be avoided → Patch together a cache A race condition appears → Add a boolean flag Stale data becomes a problem → Add another timestamp In isolation, each step seems reasonable. Taken together, however, they gradually erode the structure of the system. State becomes a catch-all container. Effects become a dumping ground for unrelated side effects and synchronization patches. The lifecycle of async work becomes tightly coupled to the UI. This is one of the biggest blind spots in current AI coding workflows: We obsess over what AI can write, while ignoring whether it knows what should never be written together. Many assume that sufficiently detailed prompts will produce a great architecture. That’s only half true. Requirements tell an AI what to do. Architectural boundaries define what must stay separated. I ran into this distinction repeatedly while building my open-source project, signal-kernel. When the core semantics of a system are explicit, the improvement goes beyond faster code generation. The agent operates within a much clearer decision space, while the architecture provides a set of natural guardrails. In signal-kernel, I deliberately removed control of the data flow from the UI framework: UI adapters for React and Vue act only as snapshot consumers. The reactive graph exclusively defines and owns dependency relationships. The async runtime owns async correctness independently of the UI lifecycle. Resources explicitly represent the lifecycle and state of asynchronous data. Mutations and Streams remain semantically distinct: Mutations carry invalidation contracts, while Streams distinguish event flow from stable values. Once these semantics are encoded into the infrastructure, an AI agent is far less likely to place every responsibility into the same layer. It does not need to guess where state belongs. It does not need to use Effects to repair synchronization gaps. It is less likely to confuse a cache with derived state. The system’s semantic map already constrains what a valid implementation looks like. I am not opposed to AI-generated code. Its value in rapid exploration, concept validation, and reducing repetitive implementation work is undeniable. My main concern with so-called vibe coding has never been that it might produce bugs. Bugs can often be caught through testing. Architectural trade-offs that were never consciously made are much harder to detect. They quietly erode the system over time. An AI-assembled system often does not hold up under sustained questioning: Why is this value stored globally instead of locally? Why does this successful Mutation modify the local cache directly instead of invalidating the relevant node? Why is a Stream event allowed to overwrite the current stable value? Which layer owns cancellation and retry behavior? What prevents two boundaries from becoming competing sources of truth? If the reasoning behind these decisions cannot be verified or explained, the system is already carrying architectural debt. Such a system may be built extremely quickly while the business logic remains simple. But once the data flow becomes more complex, the team grows, or the system encounters edge cases involving async timing and cross-boundary synchronization, the architecture begins to collapse. The system does not lack code. It lacks boundaries. AI agents have undeniably reshaped software development workflows, but they have not made architectural thinking obsolete. If anything, they've made it far more critical. As the marginal cost of producing code approaches zero, the value of engineering shifts upward—from producing implementations to defining the constraints those implementations must follow. Previously, the scarce capability was: Who can implement the feature? The increasingly scarce capability is: Who can define data-flow boundaries, design invalidation contracts, and provide a semantic map that both humans and AI agents can follow? This is why my work began with the design of a reactive library, moved into questions of Ownership, Render Boundaries, and Async Correctness, and eventually connected to AI agents. These topics may appear unrelated on the surface. At the architectural level, however, they all point to the same principle: In an era when AI can help us write more code, we need to become much clearer about which code should never be written together.
Key Takeaways
- •Across my previous articles, I’ve kept coming back to one central argument: Once UI rendering no longer owns the entire data flow, State, Derived State, Effects, and Async Work can no longer be carelessly compressed into the lifecycle of a single component. At first, this might sound like a frontend
- •This story was reported by Dev.to, covering developments in the dev space.
- •AI advancements continue to reshape industries — read the full article on Dev.to for complete coverage.
📖 Continue reading the full article:
Read Full Article on Dev.to →


