docs/adr/0002-milestone-1-vanilla-agent-architecture.mdXem bản gốc Bản dịch đầy đủ theo đúng cấu trúc tài liệu; code, liên kết và định danh kỹ thuật được giữ nguyên.
0002. Kiến trúc cột mốc 1 - coding agent vani trên thiết bị
Trạng thái: Đã chấp nhận Ngày: 2026-06-19
Bối cảnh
Cột mốc 1 xây dựng ngành dọc hoạt động đầu tiên của decode: REPL đầu cuối chạy vòng lặp Pydantic AI ReAct trên Gemini, với bộ công cụ cốt lõi, quyền yêu cầu trên mọi công cụ, điều khiển giữa lượt, lớp bộ nhớ và nhật ký phiên có thể phát lại. Thiết kế được xử lý theo từng quyết định và xác thực chéo dựa trên ba khai thác tham chiếu (mã claude, mã mở, pi) từ wiki nghiên cứu. Các quyết định này có liên quan với nhau — vòng lặp, quyền, hàng đợi, TUI, bộ nhớ và tính kiên trì củng cố lẫn nhau — vì vậy chúng được ghi ở đây dưới dạng một kiến trúc cột mốc ADR thay vì một ADR cho mỗi nhiệm vụ. Phân tích nhiệm vụ theo thứ tự nằm trong tasks/ (đặc điểm m1-vanilla-agent); ADR này ghi lại tại sao. Xem ADR-0001.
Kiến trúc hệ thống (M1)
Chế độ xem thành phần — các mô-đun bên dưới src/decode/ và dòng chảy trong quá trình. Các cạnh đứt nét đánh dấu các đường nối có chủ ý mà các mốc quan trọng sau này sẽ lấp đầy (không có chu kỳ; config/logging/entities là những chiếc lá và được bỏ qua cho rõ ràng).
flowchart TB
user(["User"])
subgraph harness["Harness (single-flight, phase machine)"]
tui["TUI: prompt_toolkit + Rich<br/>(patch_stdout, append-style)"]
queues["Queues:<br/>steering + follow-up"]
runner["Runner<br/>(turn lifecycle, abort,<br/>deferred routing)"]
end
subgraph agent["Agent loop (Pydantic AI)"]
loop["run_turn / agent.iter<br/>(stream nodes, drain steering)"]
end
gate["Permission gate<br/>(ask every tool: allow/ask/deny + mode)"]
subgraph tools["Tools (flat registry, sequential)"]
files["File I/O<br/>read, write, edit, glob, grep"]
bash["Bash<br/>(timeout, truncate, exec seam)"]
tasks["Tasks: TodoWrite (in-memory)"]
web["Web: httpx then HTML-to-Markdown"]
ask["AskUser (deferred)"]
end
subgraph memory["Memory"]
mfiles["files: AGENTS.md + MEMORY.md<br/>(cwd to root, 200 lines / 25 KB)"]
mextract["extract on exit:<br/>one sentence to MEMORY.md"]
end
gemini["Gemini<br/>(google-gla, streaming)"]
log[("Session log: JSONL<br/>.decode/sessions, --resume")]
sandbox{{"Sandbox local/Modal (M8)"}}
user <--> tui
tui -->|steer / follow-up| queues
queues --> runner
runner -->|events| tui
runner --> loop
loop <--> gemini
loop -->|tool calls| tools
loop -->|DeferredToolRequests| gate
gate -->|prompt user| runner
ask -->|question| runner
mfiles -->|instructions| loop
loop --> mextract
runner -->|new_messages per turn| log
bash -.->|swap later| sandbox
Biến vòng đời — biến các mảnh thành iter → deferred pause → resume chân; mỗi lần tạm dừng hoãn lại vừa là cổng cấp phép/AskUser (trước khi thực thi) vừa là điểm tiêm chỉ đạo.
sequenceDiagram
actor U as User
participant T as TUI
participant H as Harness
participant A as Agent (iter)
participant M as Gemini
participant G as Gate
participant X as Tool
U->>T: prompt (idle)
T->>H: enqueue, start turn (lock)
H->>A: run_turn(history + memory)
A->>M: model request (stream)
M-->>A: text deltas + tool_use
A-->>H: DeferredToolRequests (gate BEFORE exec)
Note over H: drain steering here<br/>(append user msg before resume)
H->>G: check(tool, args)
G->>T: approve? / AskUser question
U->>T: allow / deny / answer
T->>H: decision
H->>A: resume(DeferredToolResults)
A->>X: execute (if allowed)
X-->>A: result (or ToolDenied)
A->>M: next request
M-->>A: final text (no tool_use)
A-->>H: turn done
H->>T: stream answer
Note over H: drain follow-up? else idle;<br/>persist new_messages to session log
Quyết định
Chúng tôi xây dựng M1 dưới dạng một gói Python độc lập với các lựa chọn chịu tải sau:
- Vòng lặp agent thông qua Pydantic AI trên Gemini. Xây dựng mô hình thông qua
google-gla:Đường dẫn khóa API (đọcGEMINI_API_KEY); id mô hình được điều khiển theo cấu hình (settings.gemini_model). Khung sở hữu vòng lặp ReAct; chúng tôi lái xeagent.iter()để phát trực tuyến. (Hoán đổi M2 trong OpenRouter/Modal sau cùng một nhà máy.) - HITL thông qua yêu cầu công cụ trì hoãn — cả phê duyệt công cụ và
AskUsersử dụng Pydantic AIDeferredToolRequests/DeferredToolResults. Một lượt trở thành chân (iter → deferred pause → resume). Cổng khai thác được khảo sát nội tuyến, nhưng cóagent.iter()một lượt chạy là một lượt nguyên tử, vì vậy điểm dừng hoãn lại là điểm cho phép đánh lái giữa chừng một cách cơ học** (thêm thông báo lái trước khi tiếp tục) và có thể tuần tự hóa → phù hợp trong tương lai đối với M7 Kitaru HITL bền bỉ và M9 triển khai từ xa. - Quyền: hỏi mọi cuộc gọi công cụ.
PermissionGate.check()trả lạiallow/ask/deny+ amodelĩnh vực; công cụ mang theo mộtread_onlycờ (được gắn thẻ, vẫn được hỏi trong v1). Có thể mở rộng sang chế độ M3 (default/plan/edit/bypass), tự động cho phép chỉ đọc và các quy tắc được duy trì mà không cần viết lại. - Mô hình tương tác hai hàng đợi. Hàng đợi chỉ đạo đã hết trước mỗi chặng yêu cầu mô hình (chèn ranh giới, không bao giờ làm gián đoạn luồng/công cụ đang trong chuyến bay) + hàng đợi theo dõi đã hết chỉ ở ranh giới sẽ dừng. Khi bận: trơn tru
Enter= chỉ đạo,Alt+Enter= theo dõi. Khóa một lần kéo dài toàn bộ lượt rẽ nhiều chặng (pha được đặt trước lượt đầu tiênawait). Đây là Cổng ưu tiên của kiến trúc; pi là tài liệu tham khảo. - Hợp tác hủy bỏ.
Escđặt cờ; lượt dừng ở ranh giới tiếp theo (một đường chạy trốnbashđược giới hạn bởibash_timeout_s). Việc hủy cứng/ngay lập tức được hoãn lại đối với M8 (hộp cát), trong đó việc hủy bỏ một quy trình được xác định rõ ràng. - TUI qua
patch_stdout()+ đồng thờiprompt_async(), Đầu ra phong phú kiểu nối thêm phía trên dòng đầu vào liên tục (không phải trình kết xuất toàn màn hình). Lệnh gọi công cụ hiển thị khi hoàn thành để tránh hiện tượng nhấp nháy. Đường dẫn di chuyển đến trình kết xuất toàn màn hình/vi sai chỉ hoán đổituilớp. - Các công cụ cốt lõi. Tệp I/O (
readphân trang theo dòng;write/editcó cổng;editloại bỏ BOM + chuẩn hóa CRLF↔LF, khớp chính xác với khoảng trắng-mờ yêu cầu một kết quả khớp duy nhất,ModelRetrytrên 0/>1);bash(hết thời gian chờ + cắt ngắn 2000 dòng/50 KB do tràn tệp tạm thời, đằng sau mộttools/exec.pyđường may thực hiện cho M8);tasks(kiểu TodoWrite trong bộ nhớ);web(httpxNHẬN → HTML→Đánh dấu để cắt token);AskUser(hoãn lại). Thực thi tuần tự trong v1 (hàng đợi đột biến chỉ đọc song song + trên mỗi đường dẫn thực đến với M3). - Bộ nhớ. Đọc
AGENTS.md+MEMORY.mdđi bộ cwd→filesystem-root (cwd thắng nhiều nhất), giới hạn ở 200 dòng VÀ 25 KB, đưa vào hướng dẫn của agent. Quá trình dừng lại ở thư mục gốc của hệ thống tập tin (path.parent == path) chứ không phải là một.gitđiểm đánh dấu gốc repo - quy tắc đơn giản nhất không cần phương pháp phỏng đoán bố cục dự án, phù hợp với bước đi tổ tiên của các harness được khảo sát; một.git-marker stop là mức độ cứng M3 hợp lý (cặp với các chế độ cấp phép). Việc ghi lại được cwd-ghim: vào cuối phiên, một cuộc gọi giá rẻ của Gemini sẽ thêm một bản tóm tắt có một câu ghi ngày tháng vàocwd/MEMORY.md(gốc khởi động dự án - không đi bộ) - một phiên bản giảng dạy tối thiểu có chủ ý; độ nén/rút thực tế là M4. - Persistence. Nhật ký phiên JSONL chỉ nối thêm thông qua tuần tự hóa tin nhắn của Pydantic AI (dòng tiêu đề 0 + các mục nhập đã nhập) tại
.decode/sessions/<ts>_<uuid>.jsonl;decode --resumephát lại mới nhất. Độ bền của SQLite/Kitaru muộn hơn. - Kỷ luật (từ AGENTS.md).
init_logger()ở cấp độ mô-đun trong các điểm vào; một cài đặt pydanticsettingsngười độc thân; async-for-IO/sync-for-CPU; cơ sở hạ tầng nhập khẩu, không trừu tượng;tests/gươngsrc/1:1 vớifilterwarnings=["error"], lệnh gọi mô hình được thực hiện vớiTestModel/FunctionModel(không có mạng trong CI).
hậu quả
- Sự khác biệt được chấp nhận từ các harness được khảo sát: HITL hoãn lại (không phải nội tuyến) và một
patch_stdout(không phải toàn màn hình) TUI. Cả hai đều có chủ ý - cái đầu tiên mua hệ thống lái giữa chừng + HITL bền bỉ, cái thứ hai mua sự đơn giản và nâng cấp gọn gàng sau này. - Biến các mảnh ** thành nhiều
iterchân**; khóa chuyến bay đơn phải bao bọc toàn bộ lượt và tay lái chỉ tiếp cận các ranh giới yêu cầu mô hình (không bao giờ ở giữa công cụ/giữa luồng). Bạn không thể chuyển hướng một lượt cho đến khi công cụ/luồng hiện tại kết thúc. - TUI kiểu nối thêm không có vùng trực tiếp tự viết lại (các lệnh gọi công cụ quay vòng/công cụ một phần); chấp nhận được đối với M1.
- Ghi lại bộ nhớ tối thiểu là bước đệm chứ không phải bộ nhớ sản xuất.
- Các đường nối có chủ ý để lại cho các mốc quan trọng sau: trao đổi nhà cung cấp (M2); phương thức + danh mục agent (M3); đầm nén (M4); người thực thi→hộp cát (M8); trì hoãn→bền/từ xa HITL (M7/M9); điểm tắc nghẽn dấu vết duy nhất trong
run_turn(M11); Đăng ký công cụ phẳng → MCP (M12). - Rủi ro cần xác nhận trong quá trình triển khai: Pydantic AI
GoogleProviderKhóa API kwarg, id mô hình Gemini hiện tại và thông báo người dùng chỉ đạo có thể được thêm vào sơ yếu lý lịch hoãn lại (nhiệm vụ 004); API tuần tự hóa tin nhắn chính xác (tác vụ 014); rằng lời nhắc vẫn được ghim khi phát trực tuyến nhiều (tác vụ 002).