From Coding Assistant to Development Collaborator: How I Use AI Agents

From Copilot and Cursor to CLI agents, I share how I delegate tasks, verify AI-generated code, and keep engineering judgment in the loop.

C
Posts14 minutes read

I used to be fairly conservative about how I worked with AI agents.

I would usually think through the architecture first, decide how to split the core modules, and then ask an agent to handle smaller tasks: adjusting a method, adding documentation, writing tests, or checking whether a piece of code had a problem. The agent was more like a coding assistant I could consult at any time, not an engineer responsible for developing an entire feature.

Over the past few months, I have gradually changed that habit. As models have become better at handling complex projects, they are no longer limited to suggestions about a single file. They can understand the project structure, follow relationships between modules, and even help with large-scale refactors and new feature development.

That made me think about how the engineer's role should change if an agent can take on a larger part of the development work. Do we still need to implement every method ourselves? Or is it more important to define the problem, break down the work, set constraints, and verify what the agent produces?

I have also seen this shift in recent interviews. Some interviewers now use implementation tasks to evaluate not only whether you can write code, but also how you use AI, how you work with an agent, and whether you can judge whether its output is reliable.

From Tab to Chat

My experience with agent-assisted development goes back to my senior year of college. GitHub Copilot had just launched while I was preparing to graduate. At first, it was integrated into VS Code as code completion through tab.

copilot-tabs-complite

Even that first experience felt impressive. When I was still learning and Copilot did not exist, building a CRUD API and connecting it to a frontend could take me one or two days from end to end. That included searching Stack Overflow and related articles. After GitHub Copilot arrived, the development time became much shorter. It could generate code based on existing code, reduce repetitive method implementation, and sometimes implement a method directly from its name.


I worked this way until around 2024, when I made another change. Cursor was becoming popular, but I found it difficult to use at first. It was my first time modifying code through chat, and after using it for a while, I even switched back to VS Code. Cursor's tab completion was more convenient than Copilot's at the time, though.

cursor chat

What eventually made me stay with Cursor Chat was its ability to understand the project context directly from the existing codebase. In the past, when I ran into a problem, I had to collect the relevant code and error messages before copying them into a browser to search for an answer. That took time, and the answer could still be incomplete if I missed a file or a dependency.

Cursor Chat let me ask an agent about the code I was already reading or modifying, directly inside the project environment. For example, I could ask it to explain a data flow, identify the modules that might be related to an error, or help evaluate an approach. I no longer had to switch constantly between the editor and the browser or repeatedly copy and organize context.

This was the main reason I gradually moved from relying only on tab completion to collaborating with an agent through chat. tab is still useful for local and well-defined code completion. But when a problem involves multiple files or requires an understanding of the project context, talking to an agent inside the codebase fits my workflow better.

From Local Changes to Complete Tasks

I remember spending a lot of time planning and designing the modules for Donkin.ai, including its TradingView chart plugin, chat state management for the agent, and various trading-related modules.

donkin.ai

Donkin.ai

At the time, we built a decentralized investment-advisory platform that integrated large language models with multiple external data sources. It used community crawlers and wallet identification to monitor events in real time, and users could use the agent's multi-turn chat to understand the analysis of a particular token and trading wallet.

While organizing the chat architecture, I spent a lot of time figuring out how to handle server-sent events (SSE), and how to connect frontend data flows and method calls to other pages so that adding pages and tool calls later would not require redesigning the entire flow.

lobechat

I referred to the official Lightweight Charts plugin and the way LobeChat managed chat state. LobeChat also used Zustand for related state management and had a complete frontend and backend implementation to study.

My development process at that stage was close to the workflow I already knew: understand the requirements, plan the modules, study related projects, and then implement the core features step by step. The agent mainly helped with research, small pieces of code, and repetitive work. I was still responsible for the architecture and the main technical decisions.


This development model continued until the second half of this year. As I gradually moved from Cursor to CLI agents such as Claude Code and Codex, the way I worked with agents began to change.

The main reason was the change in model capabilities. Earlier models still struggled with larger tasks and with understanding an entire codebase. After Fable arrived, its ability to understand and execute these tasks improved noticeably. My own projects were also becoming more complex. Some had accumulated outdated code over time, while others had many separate modules. In those situations, an agent helped me understand the existing architecture faster, find the files that needed to change, and complete a cross-module feature in one pass. The agent was no longer limited to small, isolated tasks. Once I defined the goal and constraints, it could execute a complete task.

One thing that impressed me when I first used Fable was a project where I needed to adjust how a private package was installed and verified in GitHub Actions. Fable was the first agent I saw independently use the gh CLI to check the online logs. After narrowing down the problem, it went into another local repository—the repository containing the package that the action needed to install—changed a postinstall command, and committed the fix. I initially wanted to stop it, but the change was reasonable after I reviewed it. I have also noticed newer models doing this more often: they align themselves with the problem, verify their assumptions, and then repair the issue. Older models rarely behaved this way. They might search the web and report what they found, but their ability to verify and fix problems autonomously is much stronger now.

This change was not only about moving from a graphical user interface to the command line. My focus also shifted from code first, agent second to task first, agent-assisted execution. Previously, I would find the file that needed to change and ask the agent to modify one part of it. Later, I would describe the goal and constraints first, then let the agent read the codebase, find the relevant files, and propose an approach.

claude

As a result, the scope of what I handed to an agent expanded from individual methods and local changes to cross-module refactors and complete feature development. I also started to rethink how I break down tasks, provide context, and verify the agent's output.

How I Delegate Tasks to an Agent

The biggest change is that I no longer start by telling the agent which file or method to modify. I first describe the problem I am trying to solve and the requirements.

2026 coding agent

Confirm the Requirements, Context, and Architecture First

I may let an agent read the codebase and find the relevant files, but that does not mean I can stop understanding the existing architecture. I still need to know roughly how the project is divided, how the main data flows work, and which modules the request might affect. That understanding helps me judge whether the agent's approach makes sense and correct it when it moves away from the requirements.

Before making any changes, I usually use a few approaches to check whether the agent and I understand the problem in the same way. In one approach, I tell it the implementation direction I expect to take without explaining the underlying purpose or requirements in detail. In another, I describe the current problem and the expected result without specifying the implementation.

After explaining the problem in either way, I ask the agent to evaluate it and provide feedback before allowing it to make changes. This gives me a chance to observe how different models understand the requirements and whether their proposed solutions match what I expect.

For example, I wanted to change the authorization rules for agents on my blog so that users had to be logged in. The original plan allowed unauthenticated users to use them, but the feature was still under development, so only root could use it at that point. I first asked the agent to identify what needed to change for this requirement.

opus-5-ask

Some models, such as Opus 5, first organize the parts of the request that need clarification and then ask several questions at once. This can bring the requirements into focus more quickly, although its proposed approach may differ from what I initially expected. In general, it does a good job of organizing the points that need confirmation and may catch things I overlooked.

Other models, such as Fable, provide a suggested direction right away. They explain what needs to change and why, and may even list the expected files and the possible impact. I usually check whether the model understood the requirements correctly, then add missing constraints or correct its direction. Only after we agree on the goal do I let it start implementing.

Plan First, Then Work in Stages

Once the requirements and implementation direction are clear, I let the agent start making changes. I usually do not ask it to complete everything at once. Instead, I ask it to follow the plan we just agreed on. After each stage, I review the diff, run the tests, and continue asking questions or making adjustments based on the results. This lets me confirm that every stage is moving in the right direction.

I have also noticed that the higher token cost of advanced models is not without reason. Based on my own review experience, Fable has the smoothest workflow. It usually completes more of the task directly and does not take many unnecessary detours. Opus, by comparison, sometimes overthinks simple requests. It may break a small task into something overly complex. The task is eventually completed, but the resulting implementation is harder to understand.

This reinforced my view that choosing a model is not only about whether it can finish a task. The output also needs to be understandable, fit the existing architecture, and remain maintainable. An agent can do more of the work, but I am still responsible for the final review and technical judgment.

Delegating the Work Does Not Mean Delegating Responsibility

People often describe the impact of AI agents in terms of product value and cheaper code. Agents can certainly make features faster to implement and let engineers finish in hours what used to take much longer.

But lower code-generation costs do not automatically lower maintenance costs. If an agent does not understand the existing architecture and constraints, it may quickly produce code that appears to work for the immediate request but creates more problems as the system evolves. Code that addresses only the current request without considering the broader context can easily become slop code that someone has to clean up later.

A Type Error in ky's onUploadProgress

I have seen code pass TypeScript checks while still failing to behave as expected.

ky is an HTTP client built on the Fetch API. Its onUploadProgress callback can be used to track file upload progress. A lower-tier model once generated the following code:

onUploadProgress: (progress: unknown) => {
  const progressEvent = progress as {
    loaded: number; 
    total?: number; 
  };
  if (
    progressEvent &&
    typeof progressEvent.loaded === "number" &&
    progressEvent.total &&
    progressEvent.total > 0
  ) {
    const percent = Math.round(
      (progressEvent.loaded / progressEvent.total) * 50 + 50
    );
    setUploadItems((prev) =>
      prev.map((i) =>
        i.id === item.id ? { ...i, progress: percent } : i
      )
    );
  }
},

This code treated progress like the progress event commonly found in browser APIs and assumed that it contained loaded and total. In practice, ky's onUploadProgress uses a data structure with fields such as these:

type Progress = {
  percent: number;
  transferredBytes: number;
  totalBytes: number;
};

The correct fields are transferredBytes and totalBytes, not loaded and total. Because the code declared the parameter as unknown and then forced it into a custom type with a type assertion, TypeScript could not detect that the assumption differed from the actual API. At runtime, progressEvent.loaded was undefined, so the condition never passed and setUploadItems was never called. Unless someone checks the API documentation or follows the runtime behavior, this kind of problem is difficult to catch with type checking alone.

React's useEffect and setState

There is a similar class of problem on the frontend. With React, some models are quick to call setState inside useEffect. If the dependency array and state updates are not handled carefully, the code may work for feature A but trigger unnecessary renders—or even an infinite loop—after feature B changes the data flow or adds more state. Problems like this usually cannot be fixed by changing one line. You need to review the responsibilities of the state, the data flow, and the component architecture.

CRON Jobs and Multiple Replicas

The backend has similar failure modes. For scheduled work, if a service runs with multiple replicas, it is usually not a good idea to call setInterval inside every backend instance. Each replica may run the same job at the same time, causing duplicate processing or data races. In practice, the CRON job may belong in a separate worker or scheduling service, away from the backend that handles regular requests. But if the agent does not know the deployment model and only sees a request to run a function periodically, it may add an interval directly to the backend. That can appear to work locally or with a single instance, then behave very differently after deployment with multiple replicas.

The common thread is that an agent can produce code that looks reasonable without understanding the whole system. If an engineer checks only whether the code compiles or whether the feature works in the current scenario, the problem may not appear until the next feature, production, or future maintenance.

The condition for delegating work to an agent is that I still need to understand the existing architecture, data flows, APIs, and deployment model. An agent can help read files, organize problems, propose a solution, and even implement changes across several modules. But I am still responsible for the final technical judgment and review. If I cannot explain the code an agent produced, I should not merge it into the project.

Interviews Are Starting to Evaluate AI Use

Over the past two years of interviewing, I have increasingly seen interviewers evaluate how candidates use AI. Some give an implementation task, while others ask about the candidate's usual workflow. The first time I encountered this was last November. The task was to record myself implementing a spinner application and then add several follow-up features.

I was surprised the first time. Before that, most of my interviews had focused on whiteboard problems. Recently, though, the evaluation has gradually shifted toward how candidates use AI.

In July, Coinbase shared how it spent a year redesigning its engineering interview process and included AI fluency in every stage of the interview.

Coinbase said that AI-generated code grew from 5.7% of its merged code in the first quarter of 2025 to more than 50% in the fourth quarter of 2025. Later, nearly all new code was generated by AI, while humans still reviewed it for quality and compliance.

With this kind of workflow, senior engineers' core responsibilities also shift toward writing clear specifications, guiding AI toward an implementation, reviewing pull requests for correctness and security, finding architectural decisions that sound confident but are wrong, and evaluating trade-offs, risks, and system boundaries.

My Current View of AI Agent Development

This is a recent post shared by Vercel CEO Guillermo Rauch.

Most code may eventually look more like assembly work, and engineers may not need to implement every method themselves. But until then, software infrastructure and real users are still relying on code produced by these models. As long as a system needs to run and be maintained over time, engineers cannot give up the responsibility of reading, understanding, and verifying the code.

That is my current view of AI agents: I can delegate larger tasks to an agent, but I cannot stop reading the code. The agent can handle execution. I still need to understand what it did and remain responsible for the final architecture, risks, and results.

Last updated

Written by: Chia1104 CC BY-NC-SA 4.0

Chia1104
©
Chia1104