Think Before You Generate: Engineering Practices for Agent-Based Programming
General language models that reason only while generating tokens often drift on multi-step coding tasks. This article explains an explicit reasoning layer, interleaved think–act loops, cross-turn consistency, end-to-end agent delivery, design-token UI checks, evaluation metrics, and sandbox controls—without adding claims beyond the source.
Background: Why Generating and Reasoning at the Same Time Falls Short
Most general-purpose language models produce output by predicting one token at a time. At each step they rely on prior text for a local decision. They do not have a separate thinking stage to organize the goal, break down steps, and check conclusions. That is usually fine for simple Q&A. On multi-step tasks it shows three typical failures.
First, the logic of the output drifts. The first half of a response treats a variable as meaning one thing; the second half quietly changes the definition. The code may run, but it does not meet the original requirement.
Second, reasoning is easy to lose across turns. After the user changes a requirement once, the model may keep only the final conclusion and forget why an earlier tradeoff was made.
Third, there is no review step. The model never gets a chance to check, before it answers, whether the plan satisfies every constraint.
Separating thinking from output, and feeding the thinking result into later steps, is an important way to make complex tasks more stable.
Basic Idea of an Explicit Reasoning Layer
An explicit reasoning layer means the model produces an internal reasoning passage before it writes a formal reply or calls a tool. That passage does three things:
- Clarify the goal: turn the user request into subgoals that can be checked.
- Break down the steps: list execution order, dependencies, and risks.
- Self-check: compare a draft against the constraints, fix contradictions, then output.
This layer has a clear cost. Reasoning text uses extra tokens and increases latency and call cost. Whether users see that text depends on the product. End-user experiences usually show only the conclusion and necessary explanation, and keep the reasoning as internal state. Developer debugging can keep the full reasoning trace so problems are easier to locate.
Interleaved Thinking: Reason Before Every Action
A single reasoning pass cannot cover every task. Programming work usually loops through reading files, editing code, running tests, and fixing errors. The core of interleaved thinking is to generate a new reasoning passage before every tool call or output, and to include the previous observation in that judgment.
A typical execution loop is:
Think → choose an action → run the tool → observe the result → think again → continue or stop
The observation must enter the next round of reasoning. If an error returned by a tool is not included in the judgment, the model repeats the same edit.
The pseudocode below shows the loop structure:
def run_agent(task, tools, max_steps=20):history = [{"role": "user", "content": task}]for step in range(max_steps):reasoning, action = model.plan(history) # produce reasoning and the next actionhistory.append({"role": "assistant", "reasoning": reasoning, "action": action})if action.type == "finish":return action.answerobservation = tools.execute(action) # execute in a restricted environmenthistory.append({"role": "tool", "content": observation})raise RuntimeError("max steps exceeded; task not finished")
max_steps caps the loop so the model does not edit forever. tools.execute should run in a sandbox, with strict validation of parameters.
Reasoning Consistency: Keep State Across Turns
Reasoning consistency means the model stays aligned, across many turns, with goals, constraints, and decisions made earlier. Three points matter in practice.
First, make constraints explicit. Turn hard user requirements—language version, API compatibility, files that must not be changed—into a separate list, and place that list in a fixed part of the context on every turn.
Second, record decisions. Write each important tradeoff, such as choosing a data structure or dropping a plan, into a decision log that later turns can cite.
Third, use checkpoints. After a subtask is done, replace the long intermediate trace with a short summary. That limits context length while keeping the key facts.
The structure below is one way to manage state with constraints and a decision log:
state = {"constraints": ["keep the API backward compatible", "use the project's existing logging module"],"decisions": [{"step": 3, "choice": "use paginated queries", "reason": "the data volume exceeds the single-response limit"}],"checkpoint": "user module done; order module still pending",}
On every turn the model can see what has already been decided and which limits still apply, so contradictions become much less common.
Agent Programming: From Snippets to a Full Delivery
Traditional code generation often stops at the function level. Agent programming aims at a complete delivery: understanding the requirement, designing the approach, editing files, verifying with tests, and explaining the result. A reliable flow usually has these stages:
- Clarify requirements: identify inputs, outputs, boundary conditions, and acceptance criteria. If the requirement is ambiguous, ask first instead of assuming.
- Plan the approach: list files to change, the blast radius, and how to roll back.
- Change incrementally: edit only a small part each time, and keep the code compilable and runnable.
- Verify automatically: run unit tests, static checks, and build commands, and feed the results back to the model.
- Review and close: summarize what changed and state risks that are still uncovered.
Automatic verification is the highest-value stage. When a test fails, the error log is the most direct feedback. Giving the model the original log is more reliable than asking it to judge from memory.
Interface Generation: Correct and Visually Coherent
Besides logical correctness, front-end pages, slides, and posters have to meet visual quality. Model-generated interfaces often have inconsistent spacing, arbitrary colors, and a muddled type hierarchy. The fix is to turn design rules into executable constraints, not only descriptive prompts.
A common practice is to define design tokens for color, type size, spacing, and corner radius:
{"color": {"primary": "#1F6FEB", "text": "#1F2328", "muted": "#656D76"},"fontScale": [12, 14, 16, 20, 24, 32],"spacing": [4, 8, 12, 16, 24, 32],"radius": 8}
Requiring the model to use only those values during generation cuts visual noise sharply. After generation, a headless browser can capture screenshots, and rule checks—for example, whether text overflows its container or contrast meets the bar—can complete an automatic review.
Evaluation: Do Not Judge From a Single Generation
Evaluating agent programming needs several metrics. Subjective impression alone is easy to bias. Useful dimensions include:
- Functional correctness: pass rate on a fixed test set.
- Consistency: whether key decisions stay stable when the same requirement is run multiple times.
- Edit cost: lines or rounds a person still has to fix.
- Safety and compliance: whether the agent accesses files beyond its authority or leaks secrets.
- Visual quality: scores for alignment, hierarchy, contrast, and similar dimensions, with the scoring criteria kept.
Each metric should have a baseline. After a model or prompt change, rerun the same task set so a one-off result is not mistaken for an improvement.
Rollout Advice and Risk Control
An agent can run commands and edit files, so risk control has to come first.
- Sandbox execution: run code in an isolated environment and restrict network and file access.
