The Five Patches That Turn a Chatbot Into an Agent
Core Conclusion (Top of the Pyramid)
The essence of an Agent is to supplement an LLM with five capabilities it inherently lacks—Memory, Tool, RAG, MCP, Skills—thereby transforming a "brain that can only chat" into an intelligent entity that "can remember context, execute actions, retrieve private knowledge, access real-time information, and orchestrate complex tasks."
That is:
Agent = LLM + Memory + Tool + RAG + MCP + Skills
To use a single analogy: An LLM is a freshly graduated genius—smart but without a badge, computer, or knowledge of internal processes; an Agent is a full-fledged employee after being equipped with a badge, computer, internal wiki, and colleague directory. The most valuable AI products like Claude Code, Cursor, and Manus are essentially doing exactly this.
Below, I use three sets of arguments to prove why this formula holds, how to implement it, and why it is sufficient.
Argument 1: This formula is not a patchwork—each module is a precise solution to a specific LLM shortcoming
The six modules of an Agent are not a pile of "more is better" features. When dissected one by one, each module precisely corresponds to an inherent defect of the LLM. In other words, the Agent "grows" layer by layer from the LLM's shortcomings.
| LLM Inherent Defect | Specific Manifestation | Why It's Fatal | Corresponding Module |
|---|---|---|---|
| Stateless | Completely forgets what was discussed last week; every conversation is a "first meeting" | Cannot accumulate user context, cannot form long-term collaborative relationships | Memory (Database/Redis/Frontend Storage) |
| No Execution Capability | Can tell you "what to do," but cannot actually read files, call APIs, or write to databases | Forever stuck at the "suggestion" level, unable to generate actual value | Tool Use (Function calls, LLM outputs instructions → Runtime executes) |
| No Private Knowledge | Training data lacks your company's API docs, business rules, codebase | Strong general ability, but "hallucinates" when encountering specific business contexts | RAG (Retrieval-Augmented Generation, injects private knowledge into the Prompt) |
| Knowledge Cutoff Date | Pre-training data has a cutoff; unaware of the latest news, real-time stock prices, newly released libraries | In Chat scenarios, search can compensate, but Agent scenarios require structured data | MCP / Third-party Tool (Access external real-time capabilities via protocols) |
| Cannot Orchestrate Long Tasks | Making a PPT, analyzing the stock market and auto-trading—single-turn Q&A granularity is too coarse | Real work involves multiple steps and rounds | Skills (Combining multiple sub-tasks into reusable skill flows) |
These five defects are Mutually Exclusive and Collectively Exhaustive (MECE): Can't remember → Memory; Can't act → Tool; Doesn't know internal knowledge → RAG; Doesn't know new information → MCP; Can't do long tasks → Skills. Each module solves an independent problem, and together they cover all the capability supplements needed for an LLM to go from "chatting" to "working."
A stronger corollary can be derived from this: If you are facing a true Agent, it must implement these five modules in some form behind the scenes—missing any one of them will cause it to "slip up" in the corresponding scenario.
Argument 2: The synergy mechanism between modules determines whether an Agent can truly work—the core is the Tool invocation loop
Having the formula only tells you "what parts to install." The more critical question is: How do these parts cooperate? Because if the interfaces between parts are poorly designed, the entire Agent is just a decoration.
2.1 Overall Workflow: The LLM itself acts as the dispatch center
The Agent's dispatch center is not an external rule engine; it is the LLM itself. The entire execution flow is as follows:
User submits a complex task via Prompt
↓
LLM performs Planning / Reasoning (decomposes sub-tasks)
↓
LLM judges for itself: Need to load Memory? (Historical context, user preferences)
↓
LLM judges for itself: Need to call a Tool? (Possibly multiple, possibly multiple rounds)
↓
LLM judges for itself: Need to use RAG? (Retrieve internal knowledge → stitch into Prompt Template)
↓
Assemble final Response → Return to user
The key design here is: Each "judgment" step is not a hard-coded if-else, but is made autonomously by the LLM based on the task content. This is also why the adequacy of descriptions and schemas directly determines whether the LLM can call the correct module at the right time—the LLM reads natural language to make decisions; the fuzzier the information, the more random the judgment.
2.2 The Most Core Mechanism: The Complete Tool Invocation Loop
Among all modules, Tool is the most critical—because it is the watershed moment where an LLM goes from "can speak" to "can do." I'll break it down into two layers.
Layer 1: The Structure of a Tool itself
A Tool consists of two non-overlapping parts:
Tool = Processing Function (async, does the work)
+ Description Object
├── name: Tool identifier
├── description: Function + applicable scenarios → LLM judges "whether to call" based on this
└── schema (Zod): Parameter constraints → LLM must pass parameters according to this; this is the "contract"
The code implemented with LangChain looks like this:
import { tool } from '@langchain/core/tools';
import { z } from 'zod';
const readFileTool = tool(
async ({ filePath }) => {
const content = await fs.readFile(filePath, 'utf-8');
console.log(`[Tool Call] read_file(${filePath}) successfully read ${content.length} bytes`);
return content;
},
{
name: 'read_file',
description: 'Use this tool to read file content. Call this tool when the user asks to read a file, view code, or analyze file content.',
schema: z.object({
filePath: z.string().describe('The path of the file to read'),
}),
}
);
Three design constraints can be directly derived from this structure:
- The processing function must be async: Because reading files, calling APIs, and querying databases are all time-consuming operations; synchronous blocking would freeze the entire Agent;
- description determines accuracy: The LLM relies on the description to judge the invocation timing; the more comprehensively the scenarios are covered, the fewer misjudgments;
- schema is the invocation contract: The LLM must pass parameters according to the format agreed upon in the schema; zod performs validation; if the contract is unclear, the call will inevitably fail.
Layer 2: The Tool Invocation Flow—The LLM's "Self-Awareness"
This is the most ingenious part of the entire Agent mechanism. The LLM possesses "self-awareness"—when it judges that a Tool needs to be called, it does not fabricate an answer but stops and generates a tool_calls list declaring "I need to call these tools."
const modelWithTools = model.bindTools(tools);
const messages = [
new SystemMessage('You are a code assistant. When the user asks to read a file, immediately call read_file, wait for the result, and then analyze the code.'),
new HumanMessage('Please read the tool.mjs file content and explain the code'),
];
// First invoke: LLM does not generate text but returns tool_calls
let response = await modelWithTools.invoke(messages);
// response.tool_calls = [{ id, name: 'read_file', arguments: { filePath: 'tool.mjs' } }]
messages.push(response);
// Runtime executes the Tool, using tool_call_id to match results with calls one-to-one
if (response.tool_calls && response.tool_calls.length > 0) {
for (const toolCall of response.tool_calls) {
const toolResult = await readFileTool.invoke(toolCall.args);
messages.push(new ToolMessage({
content: toolResult,
tool_call_id: toolCall.id, // ← Key: id is the unique identifier linking the call and the result
}));
}
// Second invoke: LLM receives the tool result and generates the final reply based on it
const finalResponse = await modelWithTools.invoke(messages);
console.log('[Final Reply]', finalResponse.content);
}
This code reveals the underlying model of Tool invocation—a relay-style message loop:
HumanMessage → User: "Help me read this file"
AIMessage → LLM: "I need to call read_file, parameters as follows…" (stops, does not fabricate an answer)
ToolMessage → Runtime: "Here is the file content……" (carries tool_call_id for alignment)
AIMessage → LLM: "Okay, based on the file content, my analysis is…"
This relay model explains why tool_call_id is crucial: An Agent might call multiple Tools in a single task, and Tools are asynchronous with uncertain return times. The LLM relies on tool_call_id to precisely match each ToolMessage to each tool_call it issued—without this id mechanism, the context would inevitably become chaotic, and the LLM would be unable to distinguish which result belongs to which sub-task.
Argument 3: The performance bottleneck of an Agent lies in the Tool execution layer—Promise.all concurrency is the key optimization
After establishing the premise of "multiple Tools executing asynchronously," the next question inevitably arises: When there are no dependencies between multiple Tools, should they execute serially or in parallel?
3.1 Prerequisite Concept: Promise State Model
Before discussing parallelism, let's nail down the basic concepts:
| Concept | Definition |
|---|---|
| Promise | ES6 asynchronous abstraction, three states: Pending, Fulfilled, Rejected |
| State Transition Rules | resolve() → Pending becomes Fulfilled; reject() → Pending becomes Rejected. The two outcomes are mutually exclusive, and once the state is determined, it is irreversible |
| await | ES8 syntax, allows writing asynchronous code in a synchronous style, eliminating callback nesting |
| Promise.all | Accepts an array of Promises, executes all tasks in parallel, returns after all are complete, the result array order matches the input array order |
3.2 Serial vs. Parallel: From Principle to Data
Let's use a concrete scenario—suppose the Agent needs to fetch weather data and a tweet list simultaneously, and the two requests are independent of each other:
function getWeather() {
return new Promise((resolve) => {
setTimeout(() => resolve({ temp: 38, conditions: 'Sunny with Clouds' }), 2000);
});
}
function getTweets() {
return new Promise((resolve) => {
setTimeout(() => resolve(['I like cake', 'BBQ is good too!']), 500);
});
}
// Serial: Total time = 2000ms + 500ms = 2500ms
const weatherData = await getWeather(); // Wait 2000ms
const tweetsData = await getTweets(); // After the above finishes, wait another 500ms
// Two awaits are additive—two independent operations are waiting for each other
// Parallel: Total time = max(2000ms, 500ms) = 2000ms
const [weatherData, tweetsData] = await Promise.all([getWeather(), getTweets()]);
// Both Promises start simultaneously; the one finishing first waits for the other, and they return together at the end
This comparison directly yields a clear optimization principle: If two asynchronous operations have no data dependency, they should never be executed serially.
3.3 Impact on Agents
This principle is significantly amplified in Agent scenarios: A single complex task might call 5 mutually independent Tools—serial execution total time is t1+t2+t3+t4+t5, while Promise.all parallel execution total time is max(t1,t2,t3,t4,t5), a difference that could be several times over.
Therefore, when implementing the Agent's Tool execution loop, a layer of "dependency analysis" must be added—first distinguish which Tools have sequential dependencies (must read before write) and which are completely independent, then place the independent ones into Promise.all for concurrent execution. This is a key step in moving an Agent from "usable" to "good."
Argument 4: The completeness of this formula has been validated by existing products—Claude Code is LLM + fs + CLI
With the theory laid out, let's use a real product to verify the explanatory power of this formula. Dissecting Claude Code:
Claude Code = LLM + Tool (fs file system + CLI command line)
It is the result of pushing the Tool mechanism to its extreme: The file system Tool handles reading and writing code, the CLI Tool handles executing commands—paired with an LLM strong in programming, plus Planning and the Tool invocation loop, a Coding Agent is established.
Using the same logic to deduce a more everyday example—"Create a TodoList with React + Vite":
LLM Planning will break this task into three steps:
- Call CLI Tool →
npm create vite@latestto create the project scaffold - Call Write File Tool → LLM generates React component code, writes to
src/App.jsx - Call CLI Tool →
npm run devto start the development server
Each step is a Tool call; three calls string together into one complete programming task. This example illustrates a simple but important conclusion: The difference between an "Agent" and a "ChatBot" lies not in how powerful the underlying model is, but in whether the model is equipped with Tools that can execute operations, Memory that can manage context, and RAG that can retrieve private knowledge. Equipped, it's an Agent; not equipped, it's a ChatBot.
Implementation Roadmap: From Theory to Code
The four arguments above prove the formula's correctness (Argument 1), feasibility (Argument 2), performance requirements (Argument 3), and completeness (Argument 4). One thing remains: Build it.
The tech stack for implementation is clear and mature:
Why choose LangChain for implementation? Of course, you can manually write native APIs, handling Tool invocation logic, Message management, and Schema validation all by yourself. But this is clearly not cost-effective—LangChain's positioning is precisely an "LLM development framework"; it was born before the native OpenAI interface, essentially providing a standardized abstraction layer.
My current tech stack choice is:
Node.js (NestJS for backend)
+ LangChain (Single Agent—unified LLM interface + Tool abstraction + Message management)
+ LangGraph (Multi-Agent—for multi-agent orchestration when a single Agent is insufficient)
+ MCP / RAG / Skills (Extension capability layer)
The boundaries of the three layers are clear:
- NestJS handles the traditional backend (routing, database, middleware, permissions);
- LangChain handles the AI layer abstraction (
@langchain/openaicompatible with various models,@langchain/core/tools+ zod for tool validation and binding); - LangGraph handles multi-Agent scenarios—orchestrating multiple Agents to collaborate when a single Agent cannot handle complex workflows.
By integrating these layers through Harness Engineering, AI capabilities can be productized to realize business value (FDE).
Full Text Conclusion
┌──────────────────────────┐
│ User Prompt │
└───────────┬──────────────┘
│
▼
┌──────────────────────────┐
│ LLM Planning/Reasoning │ ← Brain: Autonomous dispatch
└───────────┬──────────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Memory │ │ Tool │ │ RAG │
│ Remember │ │ Execute │ │ Internal │
│ Context │ │ Actions │ │ Knowledge │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ ┌────────┴────────┐ │
│ ▼ ▼ │
│ ┌────────────┐ ┌────────────┐ │
│ │ MCP/3rd │ │ Skills │ │
│ │ Party │ │ Composite │ │
│ │ External │ │ Skills │ │
│ └────────────┘ └────────────┘ │
│ │ │
▼ ▼ ▼
┌──────────────────────────┐
│ Assemble Response │
└──────────────────────────┘
Three sentences to summarize my entire thought process:
- The six modules of an Agent are a precise response to the five shortcomings of an LLM—not a pile of features, but a completion of capability gaps; five sets of patches cover all missing dimensions for an LLM to go from "chatting" to "working."
- The core mechanism of an Agent is the relay-style message loop where the LLM autonomously dispatches Tools—the LLM judges for itself, calls for itself, and aligns results via tool_call_id; Promise.all concurrency for independent Tools is the key to elevating performance from "usable" to "good."
- This formula is already sufficient to build real products—Claude Code is the product of LLM + fs + CLI; the tech stack Node.js + LangChain + LangGraph + MCP/RAG/Skills can be implemented directly.
Upgrading an LLM from "eloquent" to "eloquent and capable"—this is the core proposition of Agent development.