Tự build một coding agent bằng TypeScript

Andrew Ford build từng bước một coding agent dòng lệnh bằng TypeScript và Anthropic SDK, với bốn tool read_file, list_files, edit_file, bash rồi cho nó sửa test fail.

Đã duyệt độc lập · cập nhật 25/10/2026 · 29 min

1. Từ video trước tới một coding agent dưới 330 dòng code

Andrew Ford mở đầu bằng việc nhắc lại video trước của anh trên kênh, nơi anh giải thích một coding agent hoạt động thế nào. Trong video đó có một hàm nhỏ tên calculateTotal và một test đang fail cần tìm ra nguyên nhân (theo lời anh trong video đăng ngày 25/10/2026).

Andrew Ford nói trước camera ở đầu video
Andrew Ford nói thẳng vào camera ở phần mở đầu, trước khi chuyển sang terminal.

Lần này anh muốn đi xa hơn một bước: chỉ cho người xem cách tự tạo coding agent của riêng mình, theo lời anh là trong chưa tới 330 dòng code. README của repo đi kèm (đọc ngày 25/10/2026) cũng ghi con số tương tự: khoảng 330 dòng, chia ra hai file main.ts và tools.ts, không dùng agent framework nào. Toàn bộ project viết bằng TypeScript và dùng Anthropic SDK để gọi Claude.

2. Demo đầu video: agent tự giải thích project

Trước khi viết dòng code nào, Andrew cho xem thành phẩm. Anh mở file main.ts của coding agent, chạy nó trong terminal, rồi gõ yêu cầu cho agent giải thích project hiện tại là về cái gì (demo trong video đăng ngày 25/10/2026).

Giống như ở video trước, agent bắt đầu bằng việc liệt kê tất cả các file. Terminal hiện từng dòng tool mà nó gọi: list_files, rồi read_file lần lượt với package.json, total.js và total.test.js. Tức là agent đọc nội dung các file đó vào trước khi trả lời (demo trong video đăng ngày 25/10/2026).

Terminal: câu hỏi Please explain what this project is, bốn lần gọi tool và câu trả lời của Claude
Câu hỏi "Please explain what this project is?", bốn dòng tool (list_files rồi ba lần read_file), sau đó Claude mô tả từng file và chỉ ra bug trong calculateTotal (demo trong video đăng ngày 25/10/2026).

Andrew đọc câu trả lời trên màn hình: đây là một demo project nhỏ cho video "How Coding Agents Work" của Andrew Ford, một codebase Node.js tí hon, và trông như có một bug được cố ý để lại. Anh xác nhận nhận định đó là đúng. Câu trả lời đi tiếp qua từng file, nói trong mỗi file có gì, giải thích ngắn về chúng, và cuối cùng là chính cái bug: trên màn hình ghi calculateTotal bỏ qua quantity, nên với test đầu tiên hàm trả về 3 + 6 = 9 thay vì 12. Claude cũng ghi rõ nó tìm ra điều này bằng cách đọc code, chưa chạy test (demo trong video đăng ngày 25/10/2026).

Đó chính là thứ Andrew sẽ build trong video này.

3. Coding agent là một vòng lặp

Ai đã xem video trước sẽ thấy một sơ đồ tương tự. Ý chính của nó: coding agent là một vòng lặp. Mình gửi prompt cho agent; cuộc hội thoại cùng các lần gọi tool được gửi lên API của Claude; rồi Claude hoặc trả lời bằng một yêu cầu chạy tool, hoặc in ra một đoạn text (theo cách Andrew mô tả trong video đăng ngày 25/10/2026).

Bạn gửi promptcâu hỏi hoặc yêu cầu Claude APIconversation + tool calls Yêu cầu chạy toolkết quả gửi lại ngay In ra textquay về bàn phím của bạn
Hình 3.1Vòng lặp theo sơ đồ Andrew nhắc lại: mỗi lượt gửi cả hội thoại lên Claude; nếu Claude xin chạy tool thì kết quả được gửi lại ngay, nếu Claude chỉ in text thì lượt kết thúc (video đăng ngày 25/10/2026).

4. Chuẩn bị project: Node, npm, SDK và API key

Đầu tiên cần cài Node. Andrew nói anh đang dùng Node phiên bản 26, nhưng mọi người cứ dùng bản LTS cũng được. README của repo (đọc ngày 25/10/2026) ghi yêu cầu tối thiểu là Node 20.12, vì tool list_files dạng recursive dựa vào thuộc tính parentPath.

Tiếp theo, tạo một thư mục cho project và chuyển vào thư mục đó. Dùng npm để init project; Andrew nói anh sẽ đặt type của package là module. Sau đó cài các dependency: Anthropic SDK, cùng với TypeScript, TSX và bộ type cho Node. Lệnh trên màn hình:

npm install @anthropic-ai/sdk && npm install -D tsx typescript @types/node

Thế là đủ. Khi các package đã cài xong, Andrew tạo một file .env chứa key Anthropic của anh. Anh không cho xem file đó, nhưng hướng dẫn: tạo một file .env, trong đó có một key tên ANTHROPIC_API_KEY chứa key của bạn. Để có key, phải vào website của Anthropic và tạo một API key ở đó (trang hướng dẫn lấy key có trong mục nguồn cuối bài). README của repo (đọc ngày 25/10/2026) thì ghi thêm rằng agent không tự đọc file .env, và muốn dùng file đó thì truyền cho Node bằng npx tsx --env-file=.env main.ts.

5. Bước 1: main.ts, model, màu chữ và class Agent

Code bắt đầu từ file main.ts. Trong đó việc đầu tiên là import Anthropic SDK. Sau đó Andrew dùng createInterface của Node (từ module readline). Anh chọn nó vì nó giúp lấy từng dòng từ command line thật dễ, xử lý cả các ký tự đặc biệt, và mình không phải bận tâm tự xử lý mấy chuyện đó: readline interface lo hết (theo Andrew, video đăng ngày 25/10/2026).

Ngoài ra cần vài type. Anh bắt đầu với type Message, lấy kiểu message của Anthropic từ thư viện đó, cùng với kiểu cho các tham số message (MessageParam).

Tiếp theo cần một model để làm việc. Andrew dùng model mà anh gọi là model yêu thích của mình, Opus 5.5; trên màn hình hằng số là const MODEL = "claude-opus-5-5"; (theo video đăng ngày 25/10/2026).

Vì đây là một công cụ command line, sẽ dễ nhìn hơn nếu các dòng prompt có màu khác nhau, để biết dòng nào là tin nhắn của mình và dòng nào là của Claude. Nên anh tạo một object colour với hai mục you và claude, mỗi mục là một hàm bọc chuỗi trong mã màu của terminal.

Rồi tới phần cần một class. Anh đặt tên agent là Agent và cho nó một constructor. Bên trong constructor có hai thứ: client, dĩ nhiên là client của Anthropic, và getUserMessage, một hàm trả về Promise với kiểu string hoặc null. Sau khi gõ thêm phần còn thiếu, class đã có constructor.

6. Bước 1: vòng lặp chat và runInference

Phần quan trọng nhất là một hàm bất đồng bộ (async) để chạy agent. Hàm run này chỉ chứa vòng lặp. Bên trong nó cần mảng conversation, mang theo các message param. Andrew in một dòng ra console để báo người dùng rằng họ có thể dừng bất cứ lúc nào bằng Ctrl-C, theo sau là vòng lặp while.

Trong vòng lặp, việc đầu tiên là lấy input của người dùng bằng hàm getUserMessage. Ở đây anh gõ nhầm một chỗ và sửa lại ngay. Nếu không có input thì thoát khỏi vòng lặp. Có input thì đẩy nó vào mảng conversation. Sau đó chạy inference trên các message, tức là gửi chúng lên Claude. Hàm runInference lúc này chưa tồn tại, nhưng anh sẽ viết ngay sau. Kết quả trả về từ Claude là message của assistant, và message đó cũng được đẩy vào mảng.

Editor: hàm run với mảng conversation, vòng lặp while và nhánh stop_reason refusal
Hàm run ở bước 1: mảng conversation, dòng hướng dẫn Ctrl-C, vòng lặp đọc input, gọi runInference, đẩy message của assistant vào mảng, rồi xử lý trường hợp stop_reason là "refusal".

Nếu Claude trả về một lý do dừng, cụ thể là Claude có thể trả về stop_reason là refusal (theo Andrew, video đăng ngày 25/10/2026), thì phải in điều đó ra console để mình thấy chuyện đó đã xảy ra. Trên màn hình, nhánh này in Claude: (declined to answer) rồi đi tiếp vòng lặp.

Sau đó phải thật sự in ra những gì Claude trả về, nên anh tạo một vòng lặp for đi qua phần content của message, lấy những block có type là text và in chúng ra bằng console.log.

Giờ tới hàm private runInference còn thiếu. Đây là chỗ gọi tới client. Anh đặt nhầm nó vào trong hàm khác, phải chuyển ra ngoài cho đúng chỗ. Hàm này gọi this.client.messages.create với model, max_tokens và messages là mảng conversation.

Andrew tóm lại những gì đã có: một vòng lặp while, lấy input của người dùng, đẩy nó vào mảng conversation, rồi chạy runInference.

7. Hàm main, và mảng conversation là toàn bộ bộ nhớ

Phần tiếp theo là phải có cách gọi toàn bộ quy trình này. Andrew tạo hàm main. Bên trong, tạo client của Anthropic. Rồi tạo readline interface; như anh đã nói, nó làm việc với command line, tức là standard in và standard out. Anh cũng cần biết interface đã đóng hay chưa, nên tạo một biến closed cho việc đó.

Tiếp theo là lấy message của người dùng: một hàm getUserMessage, hỏi người dùng bằng dấu nhắc "You". Rồi tạo agent. Sau đó bọc mọi thứ trong try/catch: chờ agent chạy, bắt lỗi nếu có, và cuối cùng (finally) đóng readline interface. Dòng cuối file gọi hàm main.

Andrew nhắc một điều cần nhớ: model không nhớ gì giữa các lần gọi. Vì vậy mỗi lần gọi, mình gửi lại toàn bộ cuộc hội thoại, và mảng đó chính là toàn bộ bộ nhớ của agent (theo Andrew, video đăng ngày 25/10/2026).

user userasstuser userasstuserasstuser Claude APIkhông nhớ gì giữa các lần gọi lần gọi 1lần gọi 2lần gọi 3
Hình 7.1Điều Andrew nhắc: mỗi lần gọi gửi lại cả mảng conversation, và mảng đó là toàn bộ bộ nhớ của agent (video đăng ngày 25/10/2026).

8. Bước 2: giải phẫu một tool

Bây giờ cần trao cho agent vài tool. Theo Andrew, một tool gồm bốn thứ. Thứ nhất là một cái tên (name). Thứ hai là một mô tả (description), và đây là đoạn mô tả mà model dùng để quyết định khi nào dùng tool đó. Thứ ba là một JSON schema cho input mà model phải gửi. Cuối cùng là một đoạn code: nó trả về một string, hoặc ném ra một error (video đăng ngày 25/10/2026).

Sơ đồ Anatomy of a tool: name, description, inputSchema, run(input) và bốn tool read_file, list_files, edit_file, bash
Sơ đồ "Anatomy of a tool" (ToolDefinition trong tools.ts): bên trái là bốn thành phần của một tool, bên phải là bốn tool sẽ build. Dòng chữ dưới ghi: name, description, schema được gửi cho Claude, còn run() ở lại trên máy bạn; dòng đỏ ghi lỗi quay về model dưới dạng tool_result với is_error: true (sơ đồ trong video đăng ngày 25/10/2026).

Như vậy mình đưa cho agent các tool, mỗi tool có tên, mô tả, input và thứ mà nó thực sự chạy. Andrew sẽ làm bốn tool trong video đăng ngày 25/10/2026:

  • read_file: nhận vào một path và trả về nội dung text của file.
  • list_files: nhận một path, trả về một danh sách recursive dưới dạng JSON, gồm cả các folder (trên sơ đồ ghi folder kết thúc bằng dấu /).
  • edit_file: nhận path, chuỗi cũ (old string) trong file và chuỗi mới (new string). Tool này hoặc thay thế text, hoặc tạo một file mới.
  • bash: chạy một lệnh shell. Tool này có một cái cổng: nó hỏi người dùng có cho phép chạy hay không, và chỉ chạy khi người dùng trả lời "y". Sơ đồ ghi nó trả về exit code cùng output.

9. Bước 2: ToolDefinition, gửi tool cho Claude và executeTool

Để project có tool, Andrew tạo file tools.ts chứa các tool. Đầu file cần Anthropic SDK, rồi một định nghĩa thế nào là một tool, đúng như trên sơ đồ: một interface ToolDefinition có name, description, input schema (kiểu lấy từ Anthropic SDK) và hàm run.

Quay lại main.ts, constructor của Agent giờ cần thêm một tham số private chứa danh sách tool definition. Anh thêm nó vào, và tất nhiên phải import kiểu ToolDefinition từ tools.ts; anh nói lẽ ra nên import trước.

Trong runInference, ngoài messages giờ phải truyền thêm tools cho agent. Lúc gõ phần này anh thiếu một dấu phẩy và một dấu ngoặc, sửa xong thì ổn. Hàm này map qua danh sách tool trong tools.ts và chỉ lấy ra name, description và input schema (code trong video đăng ngày 25/10/2026).

Editor: hàm runInference gửi tools cho API và hàm executeTool
runInference gọi messages.create với model, max_tokens: 16000, messages và tools (chỉ gồm name, description, input_schema). Bên dưới là executeTool: tìm tool theo tên, trả "tool not found" với is_error: true nếu không có, chạy tool.run trong try, và trả lỗi kèm is_error: true khi có exception.

Tiếp theo là hàm executeTool. Agent lúc này chưa được truyền danh sách tool definition, nên anh truyền vào; và tất nhiên agent báo lỗi vì constructor vừa đổi. Trong main, chỗ tạo agent cũng được truyền danh sách tool vào. Anh kiểm lại và thấy mọi thứ ổn cho bước này.

Ở thời điểm này chưa có gì gọi tới executeTool, và Claude cũng chưa có tool nào để mình nhờ nó chạy, vì danh sách đang rỗng: mới chỉ có phần định nghĩa (theo Andrew, video đăng ngày 25/10/2026).

Andrew chỉ ra một điều đáng chú ý trong runInference. Mỗi tool có name, description và input schema, nhưng mình không bao giờ thật sự gửi bản thân tool cho Claude. Mình chỉ đưa nó input schema; mình không gửi thứ mà tool thực sự sẽ chạy. Phần code đó không bao giờ rời khỏi máy của mình (theo cách Andrew mô tả trong video đăng ngày 25/10/2026).

10. Bước 3: tool read_file và cờ readUserInput

Tool đầu tiên, và cũng đơn giản nhất, là read_file. Andrew export nó như một ToolDefinition và đi qua từng phần: tên là read_file; mô tả là trả về nội dung của một file, cho trước path của nó tương đối với thư mục làm việc, và chỉ dùng cho file, không dùng cho thư mục. Hàm chạy dùng readFile, phải import từ Node; nó đọc file theo path trong input và trả về nội dung dạng UTF-8 khi tool được gọi. Code trên màn hình (video đăng ngày 25/10/2026):

import type Anthropic from "@anthropic-ai/sdk";
import { readFile } from "node:fs/promises";

export interface ToolDefinition {
  name: string;
  description: string;
  inputSchema: Anthropic.Tool.InputSchema;
  run: (input: Record<string, unknown>) => Promise<string>;
}

export const readFileTool: ToolDefinition = {
  name: "read_file",
  description:
    "Return the contents of a file, given its path relative to the working directory. Only use it on files, not directories.",
  inputSchema: {
    type: "object",
    properties: {
      path: { type: "string", description: "Relative path of the file to read." },
    },
    required: ["path"],
    additionalProperties: false,
  },
  run: async (input) => readFile(String(input.path), "utf8"),
};

Rồi phải làm cho tool này có mặt trong main.ts: import readFileTool vào. Sau đó là vài thay đổi trong vòng lặp. Trước vòng while, Andrew thêm một biến readUserInput, và chỉ hỏi input của người dùng khi biến đó là true: anh bọc câu lệnh lấy input và đẩy vào conversation trong một if (readUserInput), rồi dọn lại cho gọn. Trong nhánh kiểm tra refusal, readUserInput được đặt lại thành true.

Sau vòng lặp in text cần thêm một mảng toolResults. Anh nói lúc trước đã sót phần này; kiểu của nó cũng lấy từ Anthropic SDK. Trong vòng for, đến giờ mới chỉ lấy block text từ Claude. Giờ cần bắt thêm một loại block khác: block cho tool use (tool_use). Với block đó, kết quả của việc chạy tool được đẩy vào toolResults bằng await this.executeTool(...).

Lúc này lại thấy báo lỗi vì chưa thêm màu. Đã có màu cho "You" và cho Claude; giờ cần một màu cho tool, để dễ nhìn thấy, giống như ở đầu video khi thấy dòng agent đang gọi read_file.

Điều mấu chốt Andrew muốn người xem để ý ở đây: nếu Claude xin chạy tool, mình chạy chúng, gửi kết quả lại, rồi gọi Claude lại ngay lập tức mà không hỏi gì người dùng. Chỉ khi Claude không còn gì để xin nữa thì mới quay về bàn phím (theo Andrew, video đăng ngày 25/10/2026). Trong code của repo (đọc ngày 25/10/2026), đó là đoạn: nếu không có tool result nào thì đặt readUserInput = true; còn không thì đặt nó thành false và đẩy mọi tool result vào conversation trong một message duy nhất của user.

11. Bước 4: tool list_files

Tool tiếp theo là list_files. Trước hết phải thêm readdir (đọc thư mục) từ Node, rồi viết hàm của tool. Lại có tên và mô tả; mô tả mà Andrew đọc là:

List every file and directory under a path, recursively. Directories end with a slash. With no path it lists the working directory.

Tool cũng cần module path của Node, anh import thêm và phần báo lỗi biến mất. Cách nó chạy: lấy path đi vào, kiểm tra path là một chuỗi và không rỗng; nếu không phải thì dùng dấu chấm, tức là thư mục hiện tại. Sau đó đọc thư mục (recursive), map qua các entry để lấy đường dẫn tương đối, thêm dấu / cho thư mục, và trả về danh sách đã sắp xếp dưới dạng chuỗi JSON (code trong video đăng ngày 25/10/2026).

Quay lại main.ts: import tool mới, rồi tìm mảng tool chỗ có readFileTool và thêm listFilesTool vào. Phải thêm vào mảng, nếu không agent sẽ không biết tool đó tồn tại (theo Andrew, video đăng ngày 25/10/2026).

Điều Andrew thấy hay ở cách làm này: không phải sửa vòng lặp chút nào. Thêm tool chỉ là thêm một hàm; mọi thứ được giữ dạng module, vì tool đi vào qua mảng (video đăng ngày 25/10/2026).

12. Bước 5: edit_file chạy thế nào, và vì sao lỗi phải quay về model

Tool tiếp theo là edit_file. Andrew mở một sơ đồ luồng (flow diagram) về những gì xảy ra bên trong nó.

Sơ đồ edit_file: what happens, với các nhánh lỗi màu đỏ và nhánh thành công màu xanh
What happens" với input { path, old_str, new_str }: ba ô đỏ là ba lỗi ("invalid input parameters", ENOENT, "old_str not found in file"), hai ô xanh là tạo file mới và ghi file. Dòng chú thích dưới cùng: mỗi ô đỏ trở thành một tool_result với is_error: true, model đọc lỗi và thử lại (sơ đồ trong video đăng ngày 25/10/2026).

Andrew đi qua sơ đồ: input đến từ Claude kèm một path. Nếu path rỗng, hoặc chuỗi cũ giống hệt chuỗi mới, tool trả lỗi "invalid input parameters". Nếu không, câu hỏi tiếp theo là file có tồn tại không. Nếu file không tồn tại và chuỗi cũ rỗng thì tạo file mới (sơ đồ ghi: tạo folder và file, trả về "Successfully created file"); nếu chuỗi cũ không rỗng thì trả lỗi (trên sơ đồ là ENOENT). Còn nếu file có tồn tại, tool thay chuỗi cũ bằng chuỗi mới (sơ đồ ghi replaceAll, thay mọi chỗ khớp, "like Go"), rồi kiểm tra nội dung có đổi hay không. Nếu không đổi thì trả lỗi "old_str not found in file"; nếu có thì ghi file và trả về "OK" (sơ đồ trong video đăng ngày 25/10/2026).

Điều quan trọng ở đây, theo Andrew, là các lỗi. Chúng trở thành kết quả có isError bằng true, và model sẽ đọc lỗi đó rồi thử lại. Anh gọi đó là cái hay của việc để lỗi quay về đúng dạng error (theo Andrew, video đăng ngày 25/10/2026).

13. Bước 5: code của edit_file và createNewFile

Sang tools.ts, Andrew import thêm mkdir (tạo thư mục) và writeFile (ghi file), vì giờ cần sửa file. Hàm cho tool sửa file anh đã soạn sẵn, và nó có hai phần. Phần đầu là editFileTool, với mô tả anh đọc ra:

Edit a text file by replacing old_str with new_str.
old_str and new_str must be different.
To create a new file, pass a path that doesn't exist yet and an empty old_str.

Theo Andrew (video đăng ngày 25/10/2026), schema cho Claude biết tool cần những thuộc tính nào (path, old_str, new_str). Sau bước kiểm tra file có tồn tại hay chưa là nhánh tạo file. Phần thứ hai anh thêm vào là hàm createNewFile: cũng rất đơn giản, nếu path có thư mục thì tạo thư mục, nếu không thì tạo file ngay ở gốc thư mục đang làm việc, rồi ghi file. Andrew nói phần này khá dễ hiểu (video đăng ngày 25/10/2026).

Editor: thân hàm run của editFileTool và hàm createNewFile
Thân hàm run của editFileTool: ném "invalid input parameters", bắt lỗi ENOENT để gọi createNewFile khi old_str rỗng, chặn old_str rỗng với file đã có nội dung, dùng replaceAll rồi ném "old_str not found in file" nếu nội dung không đổi; bên dưới là createNewFile.

Tất nhiên phải báo cho main.ts biết: import editFileTool rồi cập nhật mảng tool một lần nữa để nó được gửi cho Claude (video đăng ngày 25/10/2026).

Quay lại tool, Andrew nhắc lại phần then chốt: khi có lỗi, mình thật sự đưa lỗi đó lại cho Claude, và nhờ có lỗi đó Claude có thể tự sửa. Anh cho rằng đây có lẽ là phần thông minh nhất mà Claude mang lại: nếu có lỗi, nó tự xử lý giúp mình (nhận định của Andrew, video đăng ngày 25/10/2026).

14. Bước 6: tool bash, time limit và giới hạn output

Tool cuối cùng là bash. Andrew gọi đây có lẽ là tool rủi ro nhất, vì nó chạy một lệnh shell, và về mặt kỹ thuật thì chạy lệnh shell là làm được bất cứ điều gì. Vì vậy anh làm nó, nhưng thêm vài biện pháp an toàn (video đăng ngày 25/10/2026).

Đầu tiên là một bash timeout: nếu script chạy quá lâu thì dừng nó. Thứ hai là chặn số ký tự output vượt quá một mức nhất định. Đó là các giới hạn đặt ở đầu phần này; trong repo (đọc ngày 25/10/2026) chúng là hai hằng số:

const BASH_TIMEOUT_MS = 30_000;
const MAX_OUTPUT_CHARS = 4_000;

Rồi tới chính hàm. Cần hai thứ: một hàm makeBashTool và một hàm chạy lệnh. Trong makeBashTool, tool tên bash có mô tả mà Andrew đọc ra:

Run a bash command in the working directory and return its output and exit code.
Use it to run scripts, tests and builds.
The user must approve each command. Commands are stopped after 30 seconds, so don't start servers or anything that waits for input.

Hàm này còn cần một hàm chạy lệnh và vài thứ phụ thuộc khác. Bước đầu tiên là xin xác nhận: phải có sự đồng ý của người dùng rằng họ muốn chạy lệnh đó. Khi đã được phép, lệnh được chạy. Để chạy, cần spawn từ child_process của Node, vì mình spawn ra một lệnh. Lệnh chạy trong process group riêng, ở chế độ detached, với cấu hình standard IO; code trong repo (đọc ngày 25/10/2026) đặt stdin là "ignore" để không lệnh nào có thể ngồi chờ input.

Rồi đến timeout: nếu lệnh chạy lâu hơn thời gian đặt trong hằng số phía trên, nó bị kill. Cũng cần một hàm truncate: nếu output quá dài, tức là dài hơn số ký tự tối đa, hàm cắt bớt và thêm một dòng ghi chú nhỏ, để không làm ngập terminal. Bản trong repo (đọc ngày 25/10/2026) giữ lại phần cuối của output, với chú thích rằng đó là chỗ có lỗi và tóm tắt kết quả test.

Andrew nói rõ đây là một tool rủi ro, vì người dùng đang uỷ quyền việc chạy một lệnh bash trong chính terminal của họ. Time limit và giới hạn output giúp agent không bị kẹt và không tự làm ngập chính nó, nhưng chúng không ngăn được một lệnh xấu. Thứ duy nhất ngăn được một lệnh xấu là người dùng đọc kỹ lệnh đó và quyết định có chạy hay không, bằng cách bấm phím Y hoặc không (theo Andrew, video đăng ngày 25/10/2026).

15. Cổng xác nhận y/N trong main

Đến đây hàm mới chỉ nằm trong tools.ts; vẫn phải cập nhật main.ts. Andrew import makeBashTool, rồi đảm bảo có bước xác nhận. Ngay dưới getUserMessage, anh thêm hàm confirm, với chú thích: mọi lệnh bash cần một chữ "y" từ bạn trước khi chạy.

Editor: hàm getUserMessage và hàm confirm hỏi Run it? [y/N]
Hàm confirm vừa thêm trong main: in lệnh bash với màu của tool, hỏi "Run it? [y/N]", và chỉ trả về true khi câu trả lời là "y". Ở thời điểm này mảng tools bên dưới vẫn mới có ba tool.

Hàm đặt câu hỏi, tô màu cho nó, và chờ câu trả lời có hoặc không. Mặc định là không; chỉ một chữ "y" mới được chấp nhận. Code trên màn hình:

// Every bash command needs a "y" from you before it runs.
const confirm = async (command: string): Promise<boolean> => {
  if (closed) return false;
  const answer = await rl.question(`${colour.tool("bash")}: ${command}\n  Run it? [y/N] `);
  return answer.trim().toLowerCase() === "y";
};

Cuối cùng, trong mảng tool phải thêm makeBashTool kèm hàm xác nhận đó. Như vậy agent đã có cổng cấp quyền (permission gate) để người dùng xác nhận rằng họ muốn chạy yêu cầu dùng tool đó (video đăng ngày 25/10/2026).

16. Chạy thử: liệt kê file và xoá notes.txt

Andrew chạy agent. Câu hỏi đầu tiên là trong thư mục có những file nào. Agent chạy tool list_files và trả về bốn file: notes.txt, package.json, total.js và total.test.js. Đây chính là bộ test từ video "How Coding Agents Work" (demo trong video đăng ngày 25/10/2026).

Tiếp theo anh muốn cho thấy sự nguy hiểm của quyền chạy lệnh, vì bash thì làm được mọi thứ. Anh nhờ agent làm một việc: "Can you remove the notes.txt file". Agent đưa ra một lệnh bash để xoá file notes.txt rồi liệt kê lại các file: rm notes.txt && ls (demo trong video đăng ngày 25/10/2026).

Terminal: list_files trả về bốn file, rồi lệnh rm notes.txt && ls chờ xác nhận Run it? [y/N]
Lượt hỏi file trong thư mục, rồi yêu cầu xoá notes.txt: dòng bash: rm notes.txt && ls hỏi "Run it? [y/N]", Andrew gõ y, và Claude báo thư mục còn package.json, total.js, total.test.js (demo trong video đăng ngày 25/10/2026).

Andrew nói việc này có thể nguy hiểm: nếu lệnh xoá hết file, có một wildcard trong đó, thì có thể có rủi ro. Nhưng ở đây rủi ro được đặt lại vào tay người dùng, và muốn chạy thì họ phải xác nhận. Anh xác nhận, và agent đã xoá file; nó còn tự kiểm tra lại và báo rằng thư mục giờ có package.json cùng các file có sẵn từ project gốc của video trước (demo trong video đăng ngày 25/10/2026).

17. Chạy thử: để agent sửa test đang fail

Agent đã sẵn sàng, giờ Andrew nhờ nó sửa test. Anh gõ: "There is an issue with the total.js file, my tests are failing".

Agent cần đọc file, và nó đã đi qua vòng lặp: đọc total.js, total.test.js và package.json. Sau đó nó gọi edit_file, và trên màn hình thấy path tới total.js, chuỗi cũ nó tìm (sum + item.price, 0) và chuỗi mới sẽ thay vào (sum + item.price * item.quantity, 0). Tiếp đó nó muốn chạy một lệnh bash để chạy npm test; lệnh trên màn hình là npm test 2>&1 | tail -15. Andrew trả lời yes bằng phím y (demo trong video đăng ngày 25/10/2026).

Terminal: ba lần read_file, edit_file sửa total.js, bash chạy npm test, và Claude báo cả hai test đã pass
Lượt sửa bug: ba lần read_file, một edit_file thêm * item.quantity, một lệnh npm test 2>&1 | tail -15 được xác nhận bằng y, rồi Claude giải thích vấn đề, cách sửa, và ghi chú về trường hợp thiếu quantity (demo trong video đăng ngày 25/10/2026).

Andrew đọc câu trả lời trên màn hình: Claude đã sửa file total.js và giờ cả hai test đều pass. Vấn đề là calculateTotal cộng giá (price) của từng item nhưng bỏ qua số lượng (quantity). Với giỏ hàng trong test, hàm trả về 3 + 6 = 9 thay vì kết quả mong đợi (3 × 2) + (6 × 1) = 12. Cách sửa: giá của mỗi item giờ được nhân với quantity của nó. Andrew nhận xét kết quả đúng như mong đợi (demo trong video đăng ngày 25/10/2026).

Câu trả lời còn một lưu ý: nếu một item không có trường quantity, dòng của nó sẽ ra NaN. Phần cuối câu trên màn hình gợi ý dùng ?? 1 để coi quantity bị thiếu là 1 (demo trong video đăng ngày 25/10/2026). Andrew kết luận: hoàn hảo.

18. Kết: hiểu agent làm gì, nhưng đừng dùng nó hằng ngày

Andrew tóm lại: đó là cách build một coding agent rất đơn giản bằng Anthropic SDK, trao cho nó vài tool để đọc, sửa, liệt kê file và tất nhiên là chạy bash script. Anh mong video có ích, và giờ người xem đã hiểu một coding agent có thể hoạt động thế nào và có thể tự viết một cái (video đăng ngày 25/10/2026).

Tuy vậy, anh không khuyên dùng agent này hằng ngày, vì rõ ràng cần thêm rất nhiều lớp kiểm tra nữa để nó an toàn. Anh khuyên cứ tiếp tục dùng những công cụ bạn đã biết như Claude Code hay Codex CLI, và lần tới khi làm việc thì thử nghĩ xem công cụ đó đang làm gì bên dưới (lời khuyên của Andrew trong video đăng ngày 25/10/2026). Anh cảm ơn mọi người đã xem và hẹn gặp ở video sau.

Nguồn và liên kết 7 nguồn

  1. Tên gốc: Building a Coding Agent
  2. Sự kiện: YouTube
  3. Video gốc: https://www.youtube.com/watch?v=4APoINtFWYs youtube.com
  4. Kênh YouTube Code with Andrew Ford (Andrew Ford): youtube.com/channel/UC3K_8j1GiBKFj4A2193e5wA youtube.com
  5. Mã nguồn của video, repo build-a-coding-agent (mỗi bước có một tag từ step-1 tới step-6; README ghi yêu cầu Node 20.12 trở lên, đọc ngày 25/10/2026): github.com/andrewjamesford/build-a-coding-agent github.com
  6. Hướng dẫn lấy API key trên Claude Platform Docs (đọc ngày 25/10/2026): platform.claude.com/docs/en/get-api-key platform.claude.com
  7. Node.js: nodejs.org nodejs.org