Series 1 - Bài 2: Architecture 4 layer của CLI Agent
Một CLI Agent được tổ chức thành 4 layer với boundary rõ ràng: CLI, Agent Core, Tool, và Context. Bài này phân tích từng layer, trách nhiệm của nó, và lý do boundary tồn tại.
Nghe bài viết này dưới dạng podcastTrước khi viết một dòng code, cần phải rõ ràng về architecture. Architecture không phải diagram để đẹp — nó là tập hợp các quyết định về ai chịu trách nhiệm cái gì và ai được phép nói chuyện với ai.
Một CLI Agent có 4 layer với trách nhiệm rõ ràng và boundary không nên bị phá vỡ.
4 Layer Overview
┌─────────────────────────────────────────────────────────┐
│ CLI LAYER │
│ Nhận input từ user, render output ra terminal │
│ Biết về: terminal, stdin/stdout, ANSI codes │
│ Không biết về: LLM, tools, context │
├─────────────────────────────────────────────────────────┤
│ AGENT CORE │
│ Orchestrate toàn bộ flow: context → LLM → dispatch │
│ Biết về: messages[], tool definitions, agentic loop │
│ Không biết về: terminal specifics, file system │
├─────────────────────────────────────────────────────────┤
│ TOOL LAYER │
│ Thực thi actions: đọc file, chạy lệnh, tìm kiếm │
│ Biết về: file system, shell, search APIs │
│ Không biết về: LLM, context, conversation history │
├─────────────────────────────────────────────────────────┤
│ CONTEXT LAYER │
│ Cung cấp thông tin về môi trường hiện tại │
│ Biết về: project structure, config files, token count│
│ Không biết về: terminal, LLM calls, tool execution │
└─────────────────────────────────────────────────────────┘
Layer 1: CLI Layer
CLI Layer là giao diện duy nhất giữa agent và người dùng. Trách nhiệm của nó rất cụ thể và giới hạn.
CLI Layer
stdin ──▶ InputHandler ──▶ CommandParser
│
┌─────────┴─────────┐
│ │
/command regular message
│ │
handle locally send to AgentCore
AgentCore ──▶ OutputRenderer ──▶ stdout
│
┌──────┴──────┐
│ │
stream text tool status
(word by word) indicator
InputHandler: Wrap readline, xử lý multiline input (khi user paste đoạn code dài), lưu command history để dùng phím mũi tên.
CommandParser: Phân biệt /commands (meta-commands như /clear, /context, /help) với regular messages sẽ được gửi đến agent. CLI Layer xử lý commands hoàn toàn, không pass xuống Agent Core.
OutputRenderer: Nhận chunks từ Agent Core và render ra terminal. Xử lý ANSI color codes, streaming từng word, progress indicator khi agent đang xử lý.
Boundary quan trọng: CLI Layer không biết gì về LLM hay tools. Nó chỉ biết cách hiển thị gì đó ra terminal và nhận input từ người dùng.
Layer 2: Agent Core
Agent Core là trung tâm điều phối — nơi agentic loop sống.
Agent Core
User message
│
▼
┌─────────────┐
│ContextBuilder│ ← System prompt + file tree + history
└──────┬──────┘
│ messages[]
▼
┌─────────────┐
│ LLMClient │ ← Gọi API, parse SSE stream
└──────┬──────┘
│ response
▼
┌─────────────────────────┐
│ Response type? │
└────┬──────────────┬─────┘
│ │
tool_use text done
│ │
▼ ▼
┌─────────┐ ┌──────────┐
│Dispatcher│ │Return to │
│execute │ │CLI Layer │
└────┬─────┘ └──────────┘
│
tool_result
│
▼
Add to messages[]
Loop lại từ đầu
ContextBuilder: Mỗi vòng loop, ContextBuilder assembly messages[] từ:
- System prompt (role, constraints, behavior)
- Long-term context (từ Context Layer)
- Conversation history (các turn trước)
- Tool results (từ vòng loop trước)
- Current user message
LLMClient: Thuần túy là HTTP client với SSE parsing. Không có business logic. Nhận messages[], trả về response chunks.
ToolDispatcher: Nhận tool_use block từ LLM response, route đến đúng tool trong Tool Layer, collect result, format thành tool_result để thêm vào messages[].
Boundary quan trọng: Agent Core không thực thi bất kỳ action cụ thể nào. Nó dispatch đến Tool Layer. Không có fs.readFile() trong Agent Core.
Layer 3: Tool Layer
Tool Layer là nơi side effects xảy ra. Đây là layer duy nhất tương tác với thế giới bên ngoài.
Tool Layer
┌─────────────────────────────────────┐
│ Tool Registry │
│ { name → Tool implementation } │
└─────────────────────────────────────┘
│
┌────────┼────────┬──────────┐
│ │ │ │
▼ ▼ ▼ ▼
FileTools ShellTool SearchTool (custom tools)
read_file run_cmd grep_files
write_file run_test find_symbol
list_dir git_status
Mỗi tool implement một interface chung:
Tool Interface:
name: string ← LLM dùng tên này để call
description: string ← LLM đọc để biết khi nào dùng
input_schema: JSONSchema ← LLM biết cần pass gì
execute(input) → result ← Application code chạy cái này
Boundary quan trọng: Tool Layer không biết về conversation history, context, hay LLM. Nó nhận structured input, thực thi action, trả structured output. Stateless hoàn toàn.
Layer 4: Context Layer
Context Layer cung cấp thông tin môi trường mà Agent Core cần để build context cho LLM.
Context Layer
┌──────────────────────────────────────────┐
│
│ AGENT.md Loader
│ └─ Đọc file cấu hình agent ở root project
│ Inject vào system prompt 1 lần khi start
│
│ FileTree Builder
│ └─ Scan cấu trúc thư mục hiện tại
│ Rebuild mỗi vòng loop (project có thể thay đổi)
│ Format gọn để không chiếm quá nhiều token
│
│ Token Counter
│ └─ Estimate token count của messages[]
│ Signal cho ContextBuilder khi gần limit
│ Quyết định phần nào cần trim
│
└──────────────────────────────────────────┘
Boundary quan trọng: Context Layer không gọi LLM và không execute tools. Nó chỉ đọc và cung cấp thông tin.
Tại sao boundaries này quan trọng
Boundaries không phải bureaucracy — chúng tạo ra properties quan trọng:
Testability: Bạn có thể test Tool Layer hoàn toàn độc lập với LLM. Không cần mock API, không cần real LLM response. Tool nhận input, trả output — đơn giản.
Replaceability: Muốn đổi từ Anthropic sang OpenAI? Chỉ thay LLMClient trong Agent Core. CLI Layer và Tool Layer không đổi.
Security isolation: Tool Layer là nơi duy nhất có side effects. Muốn audit hoặc restrict gì, bạn chỉ cần focus vào đây. Không có side effects leak ra ngoài Tool Layer.
Debuggability: Khi behavior bất ngờ, bạn biết chính xác cần inspect layer nào dựa trên symptom.
Symptom → Layer cần inspect
User input bị parse sai → CLI Layer
Context thiếu thông tin → Context Layer
LLM gọi sai tool → Agent Core (prompt) hoặc Tool Layer (schema)
Tool trả kết quả sai → Tool Layer
Loop không dừng → Agent Core (guardrails)
Violation patterns cần tránh
Anti-pattern 1: CLI Layer biết về LLM
// Sai: CLI Layer gọi LLM trực tiếp
readline.on('line', async (input) => {
const response = await callLLM(input); // ← violation
console.log(response);
});
Anti-pattern 2: Tool Layer đọc conversation history
// Sai: Tool cần biết context của conversation
class ReadFileTool {
execute(input, conversationHistory) { // ← violation
// Tool không nên cần history
}
}
Anti-pattern 3: Agent Core thực thi file operations
// Sai: Business logic lẫn với orchestration
class AgentCore {
async handleToolUse(toolUse) {
if (toolUse.name === 'read_file') {
return fs.readFile(toolUse.input.path); // ← violation
}
}
}
Kết luận
- CLI Agent có 4 layer: CLI, Agent Core, Tool, Context — mỗi layer có một trách nhiệm rõ ràng.
- CLI Layer: user interface. Agent Core: orchestration. Tool Layer: execution. Context Layer: environment info.
- Boundaries tạo ra testability, replaceability, security isolation, và debuggability.
- Khi một layer biết quá nhiều về layer khác, đó là signal của architecture violation.
- Layer boundaries không phải over-engineering — chúng là investment vào maintainability dài hạn.
Chưa có bình luận
Để lại bình luận
Bình luận sẽ được phê duyệt trước khi hiển thị.