How AI Coding Assistants Should Divide the Work: From Architectural Thinking to Code Delivery
Many developers use AI coding assistants to explain requirements, design modules, generate code, fix bugs, or even build entire features. In practice, different types of assistants perform very differently: some excel at discussing abstract concepts and organizing ideas, while others are better at working inside a codebase—reading files, modifying implementations, and running tests. This article explains how to divide work between conversational and engineering-focused assistants by task stage, using a history simulator as an example to show how correct abstraction, relationship modeling, and clear constraints determine the quality of AI-generated code. Readers will learn a practical collaboration workflow covering design discussion, documentation, module-by-module implementation, testing, and review.
In real-world development, many developers try AI coding assistants: asking them to explain requirements, design modules, generate code, fix errors, or even complete entire features. After using them for a while, however, most people notice that different types of assistants differ significantly in capability. Some tools are particularly good at discussing abstract concepts and organizing ideas; others are better suited to working inside a codebase—reading files, modifying implementations, and running tests.
The truly efficient approach is usually not simply picking one tool, but dividing work by task stage: let a conversational assistant help build understanding and explore solutions, let an engineering-focused assistant handle well-defined technical tasks, while the developer remains responsible for system boundaries, core abstractions, and final decisions.
1. Distinguish “Thinking Problems” from “Execution Problems”
Problems in software development fall roughly into two categories.
The first category is open-ended design problems, such as:
- How should the core objects in a system be abstracted?
- Who should trigger a state change?
- Should data update in real time, or propagate through events?
- How should ambiguous, erroneous, and delayed information be represented?
- Should modules interact through function calls, events, or messages?
- When future requirements change, is the current structure easy to extend?
These problems have no single correct answer. The key is not to write code immediately, but to compare the costs of different modeling approaches. Conversational assistants are usually better suited to brainstorming, listing options, spotting gaps, and explaining concepts.
The second category is execution-oriented engineering problems, such as:
- Implementing a module against a defined interface;
- Adding serialization to a data structure;
- Completing error handling and parameter validation;
- Writing unit tests for existing features;
- Modifying database migration logic;
- Refactoring duplicated code;
- Fixing implementations based on compiler and test results.
The prerequisite for these tasks is that the design is already reasonably clear. As long as constraints are explicit, engineering-focused assistants can quickly handle large amounts of repetitive work.
The problem is that many developers hand the first category of problems directly to tools as well. The typical result: the code looks complete on the surface, the number of modules keeps growing, but the overall model becomes increasingly chaotic. The tool does not entirely lack design ability—it simply tends to look for a “runnable local solution” within the current context rather than taking responsibility for the long-term consistency of the whole system.
2. In Complex Projects, Getting the Abstractions Right Matters Most
Take a history simulator as an example, where information propagation may be a core mechanic.
On the surface, the requirement seems to be simply having one character pass a message to another. But on closer analysis, information is not a simple string copy. When a person hears about an event, they may be influenced by:
- Their relationship with the message source;
- How much they trust the source;
- Their own knowledge, positions, and interests;
- The time distance from the event;
- Whether the event fits their existing beliefs;
- How many times the message has been retold;
- Whether the sender intends to conceal, exaggerate, or distort the content;
- Whether the receiver is willing to believe the message.
Therefore, information needs to be split into at least several distinct layers.
1. The Fact Layer
The fact layer describes “what actually happened in the world”—a war begins, a character dies, a disaster strikes a city. Facts should remain as stable as possible and must not be directly modified by a transmitter’s subjective description.
2. The Belief Layer
The belief layer describes what a character “thinks happened.” It may be incomplete or outright wrong. A character’s belief should never be treated as identical to objective fact.
3. The Narrative Layer
The narrative layer describes how a character tells the event to others. The same fact, retold by different characters, may produce different emphases, omissions, and distortions.
4. The Propagation Layer
The propagation layer describes how information travels from one node to another, including factors such as relationship strength, contact frequency, spatial distance, organizational structure, and transmission cost.
If these concepts are not separated at the start—if you simply design a single “message object”—it becomes easy to mix facts, memories, rumors, and text together. Later, whenever you add investigation, diplomacy, religion, family, or political systems, you will keep modifying the original structure, eventually producing unmaintainable code.
3. Relationship Networks Are Not Just Simple Graphs
Many developers know that social relationships can be represented as a graph, but “using a graph” is only a starting point, not a complete design.
A basic social network can be expressed as:
Character A ----trusts----> Character BCharacter A ----kin of----> Character CCharacter A ----rival of----> Character DCharacter A ----allied with----> Character E
But relationships in real systems usually carry more attributes:
- Relationship type: kin, friend, superior/subordinate, ally, enemy;
- Relationship strength: how close the relationship is;
- Directionality: A trusting B does not mean B trusts A;
- Change over time: relationships shift with events and history;
- Visibility: characters may not know all relationships;
- Conflicting interests: a close relationship does not guarantee agreement;
- Context: a private relationship and a political relationship may differ.
So a relationship should not be represented by a single number. A more reasonable approach is to design relationships as extensible objects, with fields such as trust, dependence, fear, intimacy, interest alignment, and time since last interaction. For uncertain information, you can also record evidence sources and confidence levels.
A practical belief record can be abstracted as:
Memory {subject: event or objectinterpretation: the character's subjective interpretationconfidence: confidence levelsources: list of information sourcescreated_at: when the memory formedupdated_at: when it was last revisedemotional_weight: emotional impact}
The key here is not the fields themselves, but making one thing explicit: a memory is a character’s internal state, not a copy of objective fact.
4. Doubt, Investigation, and Verification Should Form a Closed Loop
If the system only has two states—“received a message” and “believed the message”—information propagation will feel very mechanical. A more natural model includes the following process:
Receive information↓Initial interpretation↓Form trust or doubt↓Look for evidence↓Update beliefs↓Propagate again
Doubt is not the same as rejection. A character may temporarily hold multiple explanations and gradually adjust probabilities as new evidence arrives. For example, someone hears that an official betrayed their country. They may hold three judgments simultaneously:
- The message is entirely true;
- The event is real, but the motive was exaggerated;
- The message is a rumor fabricated by a rival.
This can be modeled with a set of candidate explanations, confidence scores, or weights. What matters is not forcing the system to commit to a single conclusion the moment a message arrives.
An investigation system should not just be “calling a function that returns true or false.” Investigation cost, the target of investigation, witness reliability, evidence completeness, and the investigator’s ability all affect the outcome. This is how the information system can genuinely interact with other systems such as politics, war, family, and economy.
5. AI Tools Work Best Filling In Well-Defined Engineering Details
Once the core abstractions are settled, AI coding tools become dramatically more effective. For example, a developer can state clearly:
- Add persistence to the memory object;
- Implement a set of relationship-update rules;
- Add tests for the message propagation process;
- Convert synchronous processing to event-driven;
- Add a unified interface for different propagation channels;
- Handle edge cases and invalid input;
- Generate documentation and examples from existing code.
These tasks share common traits: a clear goal, well-defined inputs and outputs, and relatively stable constraints. Tools can quickly generate substantial implementations, including data structures, constructors, validation logic, test cases, and helper code.
But “being able to generate a lot of code at once” does not mean you can skip review. Before generating code, clarify the following:
- Which objects represent system facts, and which represent subjective beliefs;
- Which fields are allowed to be null;
- Which operations must remain idempotent;
- Which events need to be persisted;
- Whether errors should be returned, thrown, or logged;
- How time and randomness are injected;
- Whether tests need a fixed random seed;
- How much coupling between modules is acceptable.
The clearer the constraints, the closer the generated result will be to expectations.
6. Avoid “Seemingly Safe” Over-Engineering
The most common problem with AI tools is not that they fail to work, but that they tend to over-engineer.
For example, a small data migration may balloon into two compatibility systems; a simple parameter may be wrapped in multiple layers of converters; a single function may be split into several helper classes with vague responsibilities. Each decision usually has a plausible justification, but together they increase comprehension and maintenance costs.
Common symptoms include:
- Building extensive abstractions in advance for requirements that may never appear;
- Using ambiguous intermediate layers that hide real logic;
- Passing a simple parameter through multiple wrappers;
- Splitting simple functions into too many tiny ones;
- Keeping compatibility paths that no longer serve any purpose;
- Using lengthy documentation to paper over an unclear interface.
To address these issues, follow a few principles:
Keep Interface Semantics Clear
Function names should express real behavior, not pile up abstract words like “process,” “parse,” “bridge,” or “handler.” A good name lets callers roughly understand the inputs, outputs, and side effects.
Delete Dead Compatibility Layers First
Compatibility code with no clear users becomes a permanent burden. Once a migration is complete, remove the old path promptly rather than keeping it indefinitely.
Limit the Number of Helper Classes
Before adding a new class, ask three questions: Does it own independent state? Does it have a stable responsibility? Can it be reused across multiple scenarios? If all answers are no, you usually don’t need a separate class.
Let Tests Constrain the Design
Tests do more than verify correctness—they also expose whether abstractions are muddled. If testing a simple behavior requires a pile of mock objects and complex setup, the module boundaries are probably wrong.
7. A Collaboration Workflow for the Two Types of Assistants
A relatively stable workflow looks like this.
Stage one: discuss the domain model with a conversational assistant to clarify concepts, states, events, and constraints. Do not rush to generate large amounts of code.
Stage two: distill the discussion into a short design document covering:
- Core terminology;
- Module responsibilities;
- Data structures;
- State transitions;
- Error handling;
- Invariants;
- Known open questions.
Stage three: have an engineering-focused assistant implement module by module, tackling one relatively independent goal at a time.
Stage four: run compilation, static analysis, and automated tests. Require the tool to fix issues based on real feedback, not repeated guesswork.
Stage five: manually review dependency direction, naming, duplicated logic, and exception handling—paying special attention to whether the tool introduced unnecessary abstractions.
Stage six: check the size of changes before merging. If a single modification touches the data model, propagation rules, UI, and persistence layer at the same time, it should usually be split into several smaller tasks.
Conclusion
The value of AI coding tools lies not in taking over all of a developer’s thinking, but in quickly converting well-defined ideas into runnable, verifiable implementations.
For open-ended problems, prioritize conversation, reasoning, and modeling; for problems with clear boundaries, hand them to engineering-focused tools for batch execution. The developer’s truly irreplaceable work is understanding the domain, building abstractions, setting constraints, and continuously controlling system complexity.
The most reliable collaboration model is not “let the tool design everything for me,” but “I define how the world works, and the tool implements that definition accurately.” Only when concepts like facts, beliefs, relationships, propagation, and time are correctly separated can AI tools become true engineering amplifiers, rather than code generators that keep creating new problems.
