Đưa agent cá nhân lên cloud mà không viết lại nó
Một agent chạy trên laptop thì tắt máy là mất: job theo lịch bị lỡ, điện thoại không gọi được, approval nằm chờ trong một tab đã đóng. Tài liệu này kể cách đặt nguyên engine đó vào một container trên Cloudflare: container ngủ khi rảnh, thức dậy khi có tin nhắn, job hoặc một nút Duyệt.
Đường đi của một tin nhắn, từ trên xuống
Bài toán: agent tốt trên laptop, nhưng laptop không thức 24/7
Agent cá nhân ở đây là một crew: một master agent bạn nói chuyện cùng, giao việc cho các agent chuyên trách, dùng tool, nhớ sự kiện, chạy job theo lịch và luôn hỏi trước khi làm việc làm thay đổi dữ liệu.
Chạy trên laptop
- Gập máy là job 7 giờ sáng không chạy, không ai báo.
- Ra đường không gọi được agent, trừ khi mở cổng hay dựng tunnel.
- Approval chờ trong một tab; tab đóng thì việc đứng.
- Dữ liệu, cookie, token rải trong thư mục home của máy.
Điều muốn có trên cloud
- Luôn gọi được từ điện thoại; job chạy đúng giờ dù máy tắt.
- Chỉ trả tiền compute khi agent thực sự làm việc.
- Dữ liệu cá nhân nằm trong tài khoản cloud của chính mình.
- Giữ nguyên approval, memory, tool và cách cấu hình quen thuộc.
Hai lớp, hai repository
Điểm mấu chốt của cả thiết kế: engine không biết mình đang chạy trên cloud. Nó vẫn là một process Python nghe ở 127.0.0.1:8765, y như trên laptop. Mọi thứ cloud-specific nằm ở lớp platform bao quanh.
Engine Python · public
- FastAPI + uvicorn, một process; agent loop với
max_steps. - Crew: master agent điều phối, các agent chuyên trách.
- Tool: file trong workspace, shell, web fetch/search, browser (Playwright), memory, scripts, MCP (OAuth).
- Approval có TTL, lưu trong SQLite; run chờ duyệt có status
awaiting_approval. - Memory là các fact có basis (operator / said / inferred / extracted) và khoảng hiệu lực.
- Provider: anthropic, openrouter, ollama; route chain fallback; prompt cache 1h.
- Kênh: web UI (cả phone inbox, Web Push), Telegram long-poll hoặc webhook.
Platform Cloudflare
- Worker: proxy web UI sau lớp đăng nhập, Telegram webhook, API cho edge, console; cron mỗi phút; outbound handler cho egress.
- Durable Object: một cho mỗi home, giữ handle của container, đánh thức nó và chuyển tiếp request kèm control key.
- Container (standard-1: ½ vCPU, 4 GiB): sidecar Starlette ở
:8080đứng trước engine đã pin version; shell của engine chạy trong bubblewrap. - D1: jobs, job runs, sessions, edge tasks. R2: snapshot, backup 30 ngày, drops.
- Secrets: Worker secrets thành env var của container lúc khởi động.
Engine changes go upstream. Khi cloud cần engine làm khác đi (một config key, một hook, chế độ webhook, cờ --no-schedule), thay đổi được viết như một feature trong repo engine, có test, phát hành version mới. Platform chỉ bump version đã pin.
Nhờ vậy hai bên deploy độc lập: version engine nào cũng chạy được với commit platform nào, và engine vẫn chạy được trên laptop mà không cần platform.
Kiến trúc tổng thể
Kéo để di chuyển, cuộn để phóng to. Mở viewer đầy đủ để tìm node, focus một nhánh hay trace một đường đi.
Mô phỏng: container thức dậy, làm việc, rồi ngủ
Bấm một kịch bản và xem từng bước đi qua hệ thống. Thời gian được nén: vài giây trên màn hình là vài chục giây thật, 15 phút chờ ngủ chỉ còn vài giây. Đồng hồ chi phí chỉ chạy khi container thức.
- Chọn một kịch bản ở trên để bắt đầu.
Mô phỏng dùng số đo tham khảo: cold start 12–26 s (lấy 15 s), resume sau khi duyệt 17,7 s, snapshot cuối khoảng 1,35 s, sleepAfter 15 phút, giá standard-1 khoảng 0,074 USD/giờ. Kịch bản là việc chung chung, không phải dữ liệu thật.
Một tin nhắn đi qua hệ thống
Telegram gọi webhook của Worker, kèm secret token. Worker không chờ engine: nó trả 200 ngay cho Telegram rồi xử lý tiếp, vì cold start có thể dài hơn timeout của webhook.
Durable Object của home kiểm tra container. Đang ngủ thì nó start container; sidecar kéo snapshot từ R2, mở engine, đợi engine báo sẵn sàng rồi mới nhận request. Mọi request vào container đều mang control key, và sidecar từ chối request thiếu key.
Engine chạy turn như trên laptop: master agent, tool loop, gọi model với prompt cache. Câu trả lời đi ngược về Telegram. Sau đó container ở lại 15 phút, rồi ngủ nếu không còn request.
Vòng đời container
Mô hình chi phí xoay quanh một ý: container chỉ tốn tiền khi thức. Muốn ngủ được thì không gì trong container phải tự thức theo giờ. Vì vậy scheduler và Telegram chuyển ra Worker: engine chạy với --no-schedule, và cron của Worker gọi POST /api/jobs/{id}/run khi đến hạn.
Một run đang chờ duyệt không giữ container thức. Trạng thái awaiting_approval nằm trong SQLite của engine, được snapshot lên R2 khi container ngủ. Bấm Duyệt sẽ đánh thức container, và turn chạy tiếp từ chỗ dừng.
| Đo tham khảo | Giá trị |
|---|---|
| Cold start (restore + engine ready) | 12–26 s |
| Resume turn sau khi duyệt lúc đang ngủ | 17,7 s |
| Snapshot cuối trước SIGTERM | ~1,35 s |
| Restore từ một bản backup | 36 s |
| sleepAfter | 15 phút |
R2 là nguồn sự thật, cấu hình đồng bộ kiểu git
Disk của container chỉ là cache. Chỉ container được ghi vào home trên R2; mỗi lần ghi tăng generation trong manifest, nên một bản cũ không thể đè bản mới.
Sửa trên máy, đẩy lên cloud
Home là một git repo. Lệnh update chỉ ghi commit mới nhất lên snapshot khi đó là fast-forward so với ref refs/cloud/home. Nó từ chối khi còn thay đổi chưa commit, khi có xung đột, hoặc khi cloud có commit mà máy chưa có.
Sửa trên cloud, kéo về máy
Khi bạn hay agent sửa cấu hình qua web UI, sidecar tự commit (author riêng cho cloud) trước mỗi lần đẩy home lên R2. Lệnh pull kéo về và fast-forward, hoặc merge nếu hai bên cùng sửa.
Trí nhớ: tệp Markdown, viết lại có cổng
Trí nhớ của agent là các tệp Markdown trên đĩa: không embedding, không vector DB. Bạn mở ra đọc và sửa được từng dòng. Thư mục này nằm trong home, nên được snapshot lên R2 cùng mọi thứ khác và vẫn còn nguyên sau mỗi lần container ngủ.
Hai phạm vi
Facts về bạn dùng chung cho mọi agent. Mỗi fact là một tệp, và mục lục của chúng vào prompt ở mỗi lượt. Mỗi agent có thêm MEMORY.md riêng, nằm trong system prompt, và ghi chú theo ngày. Ghi chú hôm nay và hôm qua được đặt trước tin nhắn mở lượt, nên không làm hỏng prompt cache.
Fact biết mình dựa trên gì
Mỗi fact ghi lại nó đến từ đâu: basis (operator › said › inferred › extracted), khoảng hiệu lực valid_from / valid_until, các nguồn và số phiên bản. Khi một fact được thay, bản cũ không mất mà chuyển vào lịch sử, kèm ngày kết thúc.
Tìm lại bằng grep
Phần không có trong prompt chỉ tìm được qua memory_search. Ở đây một mục là một gạch đầu dòng cùng các dòng thụt vào bên dưới. Khi so khớp, dấu tiếng Việt được bỏ qua. Facts dùng chung xếp trước, rồi đến tệp mới hơn. Kho nhỏ nên grep đủ nhanh, và kết quả là chữ người đọc được.
Hoà giải bằng code, không hỏi model
Khi một fact được ghi trong lúc trò chuyện, engine so nó với bản đang có theo một bảng luật cố định. Model chỉ đưa ra nội dung, còn code quyết định nội dung đó đi vào đâu.
| Khi ghi một fact | Kết quả |
|---|---|
| Trùng nội dung với bản đang có | Thêm nguồn, đánh dấu confirmed |
| Nội dung mới, nguồn ngang hoặc cao hơn | Thay bản cũ; bản cũ vào lịch sử với ngày kết thúc |
| Agent suy ra điều trái với lời bạn | Thành đề xuất, kèm lý do |
| Ghi từ job theo lịch | Thành đề xuất |
| Rút lại / xoá hẳn | Rút lại giữ lịch sử; xoá hẳn xoá cả lịch sử |
Cô đọng hằng tuần
Một cron đọc tối đa 7 ngày ghi chú cùng tóm tắt các cuộc trò chuyện do bạn mở, rồi để model viết lại MEMORY.md. Mỗi dòng trong tệp mang ngày (YYYY-MM-DD) của lần gần nhất nó được xác nhận.
- Dòng không có ngày, hoặc có ngày cũ hơn 90 ngày, được đưa lại cho model xem xét.
- Dòng có ngày kết thúc
(… → ngày)tự rời prompt sau ngày đó. - Nếu agent có ngân sách ký tự, mỗi dòng mới phải qua thêm một lượt sàng ngắn. Model chỉ được trả lời
NHẬNhoặcBỎ: lý do.
Cùng job này cũng biên dịch wiki của agent, và mỗi trang wiki phải liệt kê nguồn của nó.
Model viết, code quyết. Một bản viết lại chỉ được tự áp dụng khi code so bản cũ với bản mới và thấy không có gì bị mất. Nếu bản viết lại có thể làm mất một điều bạn đã nói, nó chờ bạn duyệt. Kênh nhận một thông báo, còn hộp thư có link tới trang duyệt.
Lúc bạn bấm duyệt, nếu MEMORY.md đã đổi kể từ khi có đề xuất, lệnh duyệt trả 409 thay vì đè lên. Facts dùng chung do agent quản trị dọn dẹp luôn chờ duyệt, kể cả với agent tự chủ.
Thử cổng tự áp dụng
Chọn những gì bản viết lại tuần này làm với MEMORY.md. Trong ví dụ, hôm nay là 08/11/2026.
Credential ở egress, approval ở engine
Agent đọc web, đọc email, đọc tài liệu do người khác viết, tức là luôn có nguy cơ prompt injection. Thiết kế giả định agent có thể bị lừa, và giới hạn thiệt hại bằng cấu trúc thay vì bằng lời dặn.
Token không vào container
Với API nhạy cảm (ví dụ Google), container chỉ giữ một token giả. Outbound handler của Worker thay bằng access token thật và chỉ cho qua một danh sách đóng các cặp method + path.
Approval là feature
Mọi hành động thay đổi dữ liệu đều hỏi trước, trừ lệnh bạn đã đánh dấu là quen (shell_ask_patterns). Platform chỉ phản chiếu và chuyển tiếp approval, không có công tắc “bỏ qua duyệt”.
Engine không public
Không route nào đi thẳng vào container. Chỉ Durable Object gọi được nó, kèm control key. Shell của agent chạy trong bubblewrap; HTTPS bị chặn ở egress dùng CA bundle do sidecar dựng.
Máy cá nhân là edge, không phải node thứ hai
Vẫn có vài việc cloud không làm thay được: đăng nhập trình duyệt cần màn hình, file chỉ có trên máy. Máy cá nhân phục vụ những việc đó như một edge, và không bao giờ chạy engine song song với cloud.
Chỉ poll ra ngoài
Edge agent hỏi Worker xem có việc không, chỉ chạy những loại task có trong allowlist, rồi trả output. Máy không mở cổng nào, Worker không gọi được vào máy.
Drops
Những file máy giữ cho tươi (ví dụ cookie một extension làm mới) được đẩy lên R2 thành drop, và sidecar nhặt về khi container thức. Đăng nhập một site: login trên máy, export cookie dạng Playwright storage state, gửi như một drop.
Standby và failback
Mỗi giờ máy chép snapshot về. Nếu cloud hỏng, có thể đưa crew chạy lại trên máy từ bản chép đó. Đây là đường lui, không phải chế độ chạy song song.
Một script vận hành
Deploy, update, pull, logs, cost, sessions, restore đều đi qua một script duy nhất. Số liệu chi phí và session lấy từ script đó, không ước lượng.
Chi phí: trả cho giờ thức, không phải giờ treo
Kéo thanh trượt để xem chi phí tháng thay đổi theo thói quen dùng. Phần lớn chênh lệch đến từ một câu hỏi: container thức bao nhiêu giờ mỗi ngày.
Giá niêm yết tháng 10/2026: standard-1 khoảng 0,074 USD/giờ thức, Workers Paid 5 USD/tháng, R2 vài cent. Phần model chỉ tính prefix cache (đọc khoảng 0,006 USD/lượt, ghi khoảng 0,10 USD/lần với model hạng Sonnet), chưa gồm token đầu vào mới và output. Dùng để so sánh thứ bậc, không phải hoá đơn.
Mười quyết định định hình hệ thống
Mỗi quyết định đều có cái giá. Mở từng mục để xem lý do và thứ phải đánh đổi.
Bài học khi vận hành thật
Những lỗi không thấy trong bản thiết kế, chỉ lộ ra khi hệ thống đã chạy hằng ngày.
Tự dựng: checklist
Thứ tự gợi ý nếu bạn muốn đưa agent của mình lên cloud theo cách này. Tiến độ được lưu trong trình duyệt của bạn.