Series Outline · Hand Rubbing AI Agent (Node.js / TypeScript)
This series starts from scratchGeneral tool Agent: Can have conversations, call tools, plan tasks, collaborate with multiple agents, and finally be connected to the MCP ecosystem. Technology stack lock-in Node.js 22 + TypeScriptthe model access layer uses a set of OpenAI Compatible AbstractionsTake all OpenAI / DeepSeek / Tongyi / OpenRouter – change
.envYou can change the model without any changes to the core. Every piece of code is runnable, testable, andgit checkout post-NNGo back to the starting point of any article, and the pictures and texts will be published simultaneously on multiple platforms.
General catalog (13 main lines + 1 ending)
- Beginning: What is Agent, why not just use the framework (This article)
- Engineering foundation: Node22 + TS + unified LLM client
- Heart: Agent Loop and ReAct
- Growing arms and legs: Tool system design
- The first real tool: search/calculate/read files
- Two forms of memory
- Will planning: Plan-and-Execute
- Multi-Agent collaboration
- Connecting to the outside world: MCP in action
- Make the Agent trustworthy: permissions/sandbox/manual intervention
- Persistence and recovery: session, state, crash continuation
- Online and observable
- Closing: Our hand-rubbed vs. industry ADK/Mastra
Current article: Part 1 – Open up the shortest path of “models can talk” and determine the access layer for the entire series.
Supporting code warehouse: (Each article corresponds to one
post-NNtag,git checkout post-01You can return to the starting point of this code)
When many people hear the term “AI Agent” for the first time, they think it is a smarter model. Not really. Agent is not a model or an API call, but aLet the model run repeatedly in the cycle of “thinking-action-observation”. This series uses Node.js to create a universal tool assistant from the bottom. Each code can be run, tested, and checked out. This article will first explain “what it is” thoroughly, and then lay down the technical foundation of the entire series.
00 Introduction: From “being able to chat” to “being able to do things”
Before talking about the definition, let’s establish an intuition first.
Everyone has used a chatbot: you type a sentence and it returns a sentence. The whole process is “text in, text out”. It’s like a man who is locked in a room, well-read but can’t go out – you pass a note and ask a question, and he passes a note and answers. He can’t check today’s real-time weather, open your files, or help you calculate an account you didn’t tell him, because hewithout any “hands”。
What AI Agent wants to solve is precisely the problem of “making it grow hands”.
To give a minimal example:Check the temperature in Beijing today and calculate the temperature difference between it and Shanghai.. This sentence is very simple for humans, but it is a deadlock for pure chatbots – its training data does not include “today’s” real-time weather, and it has no obligation to calculate it. When an Agent receives this sentence, it will naturally break it into several steps: first use the search tool to check the temperature in Beijing, check the temperature in Shanghai, then subtract the two numbers, and finally report back the result. If a city cannot be found, it will change the keyword and try again.
Pay attention to the key here: the agent does not “spit out” the answer all at once;Think first, then act, see the results, then think, then actuntil the task is completed. This ability to “run over and over again” is the real dividing line between Agent and ordinary completion. This article will not talk about how to write loops (that is the third article), but you must first plant this mental model, otherwise all subsequent codes will be like scratching the surface.
01 Definition: What exactly is Agent?
1.1 A formula that is poorly explained but not explained clearly
Around 2024, the community gave Agent a consensus formula:
Agent = LLM + Memory + Tools + Planning + Action
This formula is correct, but it is too “noun-stacked”. After reading it, beginners still don’t know the difference between Agent and chat robot. Translate five words into human language:
- LLM(brain): Responsible for understanding and decision-making. It reads your question and determines “what to do now”. It also reads the results returned by the tool and determines “what does this mean and what to do next.”
- Memory: Remember things. In the short term, remember what was discussed in this round of conversation, and in the long term, remember the knowledge or preferences accumulated across conversations. Without memory, Agent’s every sentence is amnesiac.
- Tools (hands and feet): Expand capability boundaries. Searching, calculating, reading files, and calling external APIs are all tools. No matter how smart your brain is, without hands and feet you can only “talk on paper”.
- Planning (task breakdown): Break big goals into small executable steps. For example, “Writing a competitive product research” will be broken down into “collecting information → summarizing differences → finalizing a draft”.
- Action: Really call the tool out and feed the results back to the brain.
These five components alone are not enough. they wantStrung into a loop that automatically rotatesis the Agent.
1.2 The real core is the cycle, not the components
Upgrade the above formula into a more essential sentence:
Adjust once
chat.completionsis “able to chat”; let itDecide to adjust the tools yourself, see the results, and then decide the next stepis the Agent.
The “make your own decision” in this sentence is a cycle. Drawn as a flow chart it is:
┌─────────────────────────────────────┐
│ │
▼ │
思考(think) ──► 行动(act: 调工具) ──► 观察(observe: 看结果)
│ │
└─────────────────────────────────────┘
Each lap: the model first thinks about “what should I do now” (it may answer directly, or decide to adjust a tool); then performs this action; after getting the result, the model then thinks about “is it enough, what is left?” The final answer is not output until the model considers the task complete.
Compare this: one-shot completion is like a straight line, input → output, end of the line. Agent is a circuit that turns corners, turns back, and takes a few more steps. In Part 3, we will use code to actually run this loop; now just remember——Loop is the soul of Agent。
1.3 Three intuitive scenarios to help you determine “whether this is an agent”
- Scenario A: Type in the input box and press Enter to get the answer. → This is a chatbot,no Agent (no loops, no tools).
- Scenario B: You say “help me book a flight to Beijing tomorrow.” It first checks the flight, compares prices, asks for your preferences, and places an order. → This is an Agent (with tools, cycles, and autonomous decision-making).
- Scenario C: You ask “What is the sum of 1 to 100?” and it directly answers 5050. → This may be the ability of the model itself,Not necessarily Agent – unless it actually “calls the calculator tool” rather than calculating from memory.
There is only one judgment criterion:Whether the model autonomously and multiple times decides “what to do next” in a task, and actually performs external actions. Yes, it is an Agent; no, it is just a chatting model.
1.4 Evolution in 2026: From “Five Elements” to “Harness”
By 2026, this formula has been pushed one step further by engineering practice. Lil’Log (Lilian Weng, head of OpenAI security system), in his 2026-07 review, named “the entire orchestration system that supports the running of the Agent in addition to the model”. Harness(It means “harness” – the model is a horse, and the harness is something that is put on the horse to let it pull the cart).
Harness adds four additional things on top of the five elements:
- Workflow loop:
plan → execute → observe → improvewhich is the engineered version of the loop in Section 1.2. - Evaluation system: Automatically determine whether the Agent has done this step correctly and how to correct it if it is wrong.
- Permission control: Whether files can be deleted, whether money can be spent, and whether dangerous operations require confirmation.
- persistent state: I crashed halfway through the run. Can I pick up where I left off instead of starting all over again?
The cores of coding agents such as Claude Code and Codex all look like this. The first few articles in this series will start with the five elements and loops (this is the skeleton), and then gradually add the Harness four-piece set.
02 A quick overview of the three bases (why you need to rub them with your hands)
Since the industry already has a ready-made framework, why should we write it from scratch? Let’s first lay out the three mainstream TypeScript bases for comparison:
| base | language | hard constraints | core competencies |
|---|---|---|---|
| Google ADK for TS | TypeScript | Node ≥ 20.19 | Code priority, Zod verification, multi-Agent orchestration, MCP, A2A protocol |
| Master | TypeScript | Nothing special | A set of interfaces to connect to 40+ model providers, agents/workflows/MCP/evaluation |
| Blade Agent SDK | TypeScript | Node ≥ 22.14 | session-first, built-in 17 standard tools + sub-Agent + permission sandbox |
All three are available, but none of them are in the main line of this series. The purpose of hand rubbing is not to “reinvent the wheel against the framework”, but to:The framework fully encapsulates loops, tool scheduling, and context management. Only by writing it yourself can you understand the stack information reported by the framework and know where the performance bottleneck is. The frame is the counterpoint to the concluding chapter (Part 13), not the starting point. First, clear the foundation and core by yourself, and then look back at the frame, and you will have the feeling of “that’s how it was.”
03 The project goals and access layer design of this article
This article will not write about loops (that is the third article), but first open up the shortest path of “models can talk” and determine the access layer of the entire series.
There is only one principle for the access layer:model independent. The Chat Completions interface formats of DeepSeek, Tongyi, OpenRouter, and OpenAI are the same, and the only difference is base_url and model name. We seal this layer into LLMClientall subsequent Agent logic only relies on it, and the kernel has zero changes when changing models.
It provides two capabilities:
chat(): Non-streaming, get back the complete text at once. Suitable for internal calls and batch tasks.chatStream(): Streaming, output token by token. The “typewriter” effect is only available in the CLI, and the user experience is completely different.
The core is streaming parsing. OpenAI’s streaming response is SSE (Server-Sent Events): each data: The line is a delta fragment, and the end of the stream is data: [DONE]. The parsing loop is very short, but there are several details that are easy to get stuck in (buffer splicing, bad line fault tolerance):
const reader = body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() ?? ""; // 最后一行可能不完整,留到下次
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed.startsWith("data:")) continue;
const payload = trimmed.slice(5).trim();
if (payload === "[DONE]") return;
const json = JSON.parse(payload);
const delta = json.choices?.[0]?.delta?.content;
if (delta) yield delta;
}
}
Non-streaming just puts stream: false Send it, take it choices[0].message.content. Unification of error branches: non-2xx is thrown directly LLMClient.chatStream failed: 状态码 响应体preventing underlying fetch errors from streaking to the business layer.
There are two more points in the project: dependency locking is Zero runtime third-party package(Only relies on Node 22 built-in fetch and Response stream), access .env Give the job to dotenv Do it at the application entrance, and the library itself remains pure.
matching src/index.ts It is an interactive REPL. As a demo for this article: enter a sentence in the terminal (with 你: prefix), the model streams a reply (with 助手: prefix),messages The array accumulates context between rounds. This is the prototype of the subsequent Agent Loop shell – first make the “multiple rounds of dialogue” run smoothly, and then add tool calls to the loop in the next article.
The data flow looks like this:

How to run the code in this chapter
All supporting codes have been pushed to GitHub, one for each article post-NN tag. Following this chapter, first cut to post-01 The code status, and then run step by step:
# 1. 克隆仓库
git clone .git
cd agent-from-scratch
# 2. 切到本篇(第 1 篇)对应的代码快照
git checkout post-01
# 3. 安装依赖(需要 Node.js >= 22,否则内置 fetch 不可用)
npm install
# 4. 配置密钥:复制模板,填入你的 API Key
cp .env.example .env
# 然后编辑 .env,填入:
# OPENAI_API_KEY=sk-你的key
# BASE_URL= # DeepSeek 兼容地址,注意【不带 /v1】
# MODEL=deepseek-flash
# 5. 启动交互式对话(输入一句、模型流式回复一句,输入 exit 退出)
npm run dev
# 6. 跑单元测试(mock,无需 key / 外网)
npm test
# 7. 类型检查
npm run typecheck
illustrate:.env has been .gitignore Ignore, the submission will not be entered, and the key will only stay on the local machine;node_modules Also ignored. like npm run dev Connection timeout reported, please confirm first .env inside BASE_URL Not written in tape /v1 address (DeepSeek does not carry /v1)。
04 Verification: What does the code in this chapter do and what effect is achieved?
Code location:src/llm/LLMClient.ts + src/index.ts(Interactive conversation REPL demo) + tests/llm.test.ts。
what did: Encapsulates a layer of model-independent conversation client, supports both non-streaming and streaming calls, unifies error handling, and converges key/endpoint configuration to .env。
What effect was achievedthree sets of evidence:

- Unit testing 4/4 passed(
npm test). Four branches are covered with mock fetch: non-streaming normal parsing, non-2xx error throwing, SSE incremental splicing, and bad line fault tolerance. This group proves that the SSE parsing logic itself is correct and does not rely on the real network. - type checking 0 errors(
npm run typecheck,tsc --noEmit)。strict+noUncheckedIndexedAccessWhen fully enabled, index out-of-bounds and null values will be blocked at compile time. - No key guard is in effect (
npm run devNot configured.env(time): The process exits safely and prints “OPENAI_API_KEY is missing, please copy .env.example to .env and fill it in”. The request will not be made with an empty key.
In this step of real dialogue, there are two engineering pitfalls that must be explained clearly. They directly determine whether the local network can be run through:
- Pitfall 1: DeepSeek
base_urlWithout/v1. The official compatible address is(model namedeepseek-flash), not the official OpenAI one.../v1. If the wrong path is configured, 401 will be returned and it is already in the warehouse..env.exampleFixed. - Pitfall 2: dotenv does not overwrite existing environment variables by default. If the running environment (local terminal/CI/sandbox) has been injected
OPENAI_API_KEY,import "dotenv/config"The default behavior is “existing variables are not overwritten”,.envThe configuration in will be silently ignored, and the request will actually be sent to other keys, which will be returned by DeepSeek.api key ... is invalid. The correct approach is to change the entrance todotenv.config({ override: true })let the local.envpriority.
After repair, use DeepSeek endpoint to measure multiple rounds of interaction and output ◇ injected env (3) from .env confirm .env Effective, terminal input questions and model streaming responses appear alternately, and multiple rounds of context are maintained:

Conclusion: The access layer delivered in this chapterAll verification of logic and real calls is available——None of the non-streaming, streaming, wrong branches, and real model calls are missed.
05 The minefield buried in this article
src/index.ts Now it is an interactive REPL, which can provide multiple rounds of input and sentence-by-sentence responses, but each round is still a one-way completion of “user says → model answers”.No think → act → observe In a loop, the model cannot decide to call the tool, see the results, and then decide the next step. All “intelligence” stops at a single response.
Next article (Agent Loop) to be added think → act → observe The cycle allows the model to decide to call tools, see the results, and then decide on the next step. That’s the real dividing line between Agent and “advanced completion”.
Judgment: Being able to talk does not mean that you are an agent. Looping is the watershed. Highlights: The next article lets you “hands-on” the model for the first time.


Focus on Internet product development and share tool experience. Do everything well, big or small.