Từ OOP đến Cordis: thử áp dụng triết lý DeepSeek Harness vào một CMS multi-tenant
Xin chào mọi người, mình là fishperson113. Mục tiêu của bài hôm nay chủ yếu là chia sẻ một số góc nhìn của mình dưới góc độ một Kỹ sư Phần mềm, sau khi đã dành một khoảng thời gian ngâm cứu deepseek-harness.
Lưu ý: Đây là một số góc nhìn mang tính chủ quan của cá nhân người viết. Mọi người cứ thoải mái thảo luận nếu thấy có gì đó sai sai.
PHẦN 01 — AGENT HARNESS VÀ DEEPSEEK HARNESS
Vậy deepseek-harness là gì? DeepSeek Harness là một dự án OSS do chính DeepSeek phát triển. Nếu phải so sánh, nó nằm trong cùng vùng bài toán với những agent harness dành cho coding agent như Claude Code, Codex, OpenCode hay Hermes Agent: tức là lớp phần mềm đứng giữa foundation model và môi trường mà coding agent thực sự làm việc.
Xin phép bổ sung một chút ngữ cảnh cho những người chưa rõ sự khác biệt giữa foundation model và agent harness. Một coding agent có thể hiểu đơn giản là sự kết hợp của hai phần. Phần thứ nhất là foundation model, a.k.a “bộ não” LLM đã được pre-train và có thể được fine-tune trên những tập dữ liệu lớn. Phần thứ hai là agent harness: agent loop, system prompt, tool definition và tool execution, context, memory, permission, session, logging... Agent harness quyết định model nhìn thấy gì, được phép làm gì và sẽ tiếp tục hành động ra sao sau mỗi tool call.

Hình 1. ReAct loop luân phiên giữa suy luận (Thought), hành động (Action) và quan sát kết quả (Observation) cho tới khi agent đủ thông tin để đưa ra câu trả lời.
Nguồn: ReAct paper mô tả cách đan xen suy luận và hành động trong cùng một vòng lặp.
Hiện tại nhiều agent harness đã cho phép lựa chọn hoặc tích hợp model từ provider khác. Vì vậy nếu thích workflow của harness này nhưng lại thích model kia, người dùng có thể linh hoạt kết hợp chúng. Tuy nhiên việc đổi model không phải lúc nào cũng hoàn toàn “cắm vào là chạy”, vì mỗi model vẫn có context window, tool-calling behavior, tokenizer và những capability riêng. Cá nhân mình chẳng hạn, mình khá thích dùng model GPT cùng với harness của Hermes Agent.
Vậy điều gì làm deepseek-harness trở nên khác biệt? Một trong những mục tiêu được nhấn mạnh là thiết kế agent runtime gọn hơn và tiết kiệm ngữ cảnh/token hơn. Tuy nhiên sau khi đọc tài liệu, mình vẫn chưa thấy một benchmark đủ rõ để kết luận nó tiết kiệm chính xác bao nhiêu phần trăm so với các harness khác. Muốn so sánh công bằng còn phải hỏi: có dùng cùng foundation model không, cùng bài toán không, tool set có giống nhau không, và chất lượng hành vi cuối cùng được đo như thế nào?
Nhưng đọc kỹ hơn thì mình thấy nếu chỉ tóm DeepSeek Harness thành “gom nhiều bước của agent loop vào một batch để tiết kiệm token” sẽ bỏ lỡ phần quan trọng nhất. Repository tự mô tả kiến trúc của mình là everything-is-a-plugin và dùng Cordis làm nền. Tối ưu token hay performance là lợi ích vận hành có thể đo sau; luận điểm kiến trúc lớn hơn là một agent harness có thể thay đổi capability ngay khi đang chạy mà vẫn có cách gỡ bỏ, hoàn tác và điều phối dependency của chúng.

Hình 2. Turn flow của DeepSeek Harness: mỗi step gồm một model request và các tool call; agent loop tiếp tục cho tới khi không còn step nào cần xử lý. Nguồn: DeepSeek Harness Architecture.
Đọc thêm: DeepSeek Harness repository · Architecture
PHẦN 02 — CORDIS LÀ CÁI GIỐNG GÌ?
Trước khi nói Cordis, mình muốn quay lại OOP một chút. OOP mang lại đóng gói (encapsulation), trừu tượng hóa (abstraction), kế thừa (inheritance) và đa hình (polymorphism), nhưng khi một hệ thống OOP truyền thống scale quá lớn, nhiều tầng kế thừa, abstract factory, interface, wrapper và container có thể đẩy dependency vào trạng thái “OOP hell”, OOP thành Oops: ta khó biết hành vi thực sự nằm ở đâu và thay đổi một base class có thể lan qua cả hierarchy. Vì vậy lập trình hiện đại thường ưu tiên composition over inheritance, ghép các component nhỏ qua contract bằng dependency injection, middleware, hook hay plugin system. Tuy nhiên composition mới giải quyết cách lắp các phần với nhau; nó chưa tự đảm bảo rằng khi một component bị tháo giữa runtime, timer, listener, connection, service, child plugin và các dependency liên quan sẽ được dọn đúng thứ tự mà không làm component khác crash.

Hình 3. Khi câu “OOP là tương lai” đi qua đủ vài tầng AbstractFactory, Wrapper và Container :V

Hình 4. Khi abstraction và dependency phát triển quá mức, cấu trúc OOP có thể trở thành một mê cung khó lần theo.
Đây là lý do mình thấy Cordis khá thú vị. Paper bắt đầu từ nhu cầu liên kết động (dynamic composition): component có thể được nạp, gỡ và cấu hình lại khi chương trình vẫn đang chạy. Trường hợp ứng dụng mạnh mẽ nhất theo như trong paper claim là self-evolving agent harness - một dạng harness trong tương lai có thể giúp agent tự sinh ra code rồi thay thế tool, memory, permission layer, execution environment hoặc thành phần điều phối của chính nó trong khi vẫn phục vụ request. Khi hệ thống càng tự phát triển, component mới không phải lúc nào cũng đúng; đôi khi bản cũ phải được thay, rollback hoặc đập bỏ hoàn toàn. Vì vậy khả năng plug-in chỉ là một nửa của bài toán; plug-out an toàn mà không crash mới là phần khó cần được giải quyết.
Paper tách vấn đề này thành hai chiều. Khả năng kết hợp theo thời gian (temporal composability) yêu cầu khi tháo một component, những tác động mà nó đã tạo ra phải được hoàn tác an toàn. Khả năng kết hợp theo không gian (spatial composability) yêu cầu component khai báo dependency và runtime phải phản ứng khi dependency xuất hiện, biến mất hoặc đổi provider. Cordis hiện thực hóa hai chiều đó bằng revertible effect, reactive coeffect, context và fiber lifecycle.
Khái niệm gốc: Cordis paper · Cordis Primer · Capability Seams
Để hai khái niệm này bớt trừu tượng, phần dưới sẽ giải thích lại chúng bằng một pipeline Web Push quen thuộc trong 1 Progressive Web Application (PWA).
Một pipeline quen thuộc trong PWA có thể là:
Luồng nghiệp vụ
Push Campaign → Notification Worker → Web Push Service → Push Subscription → thiết bị người dùng
Trong ví dụ này, khả năng kết hợp theo thời gian mô tả toàn bộ vòng đời của Web Push Service và những effect mà nó tạo ra:
Vòng đời theo thời gian (temporal)
Plug-in Web Push Service → mở HTTP client và connection → đăng ký listener cùng consumer của hàng đợi → tạo retry timer → xử lý job → plug-out → dừng nhận job mới → hủy timer → gỡ listener và consumer → đóng connection → thu hồi service
“Hoàn tác” ở đây là dọn sạch những effect mà Web Push Service đã tạo ra trước khi rời runtime, không phải hoàn tác thao tác plug-out.
Khả năng kết hợp theo không gian lại nhìn vào quan hệ dependency giữa những component đang cùng tồn tại trong runtime:
Quan hệ phụ thuộc theo không gian (spatial)
Notification Worker → phụ thuộc vào → Web Push Service
Nếu Web Push provider biến mất, Cordis unload Notification Worker trước rồi mới tháo provider cũ. Khi provider mới xuất hiện và dependency lại được thỏa mãn, Notification Worker mới được load lại:
Thay provider an toàn
Web Push Service biến mất → unload Notification Worker → tháo provider cũ → nạp provider mới → load lại Notification Worker → tiếp tục xử lý job
Ví dụ trên vẫn còn khá đơn giản vì dependency gần như nằm trên một đường thẳng. Trong hệ plugin thật, một provider có thể được nhiều plugin con hoặc plugin phụ thuộc cùng sử dụng; mỗi plugin đó lại có thể cung cấp service cho những nhánh khác. Gỡ một provider nằm giữa đồ thị lúc này có thể tạo ra cả một đợt cascade:
Cascade trong dependency graph
Web Push Service biến mất → unload Notification Worker và Retry Coordinator → unload tiếp Campaign Scheduler và Delivery Metrics → hoàn tác effect của từng fiber → tháo provider cũ
Hình 5. Topology của pipeline Web Push: dependency graph cho biết nhánh nào phải unload theo cascade khi Web Push Service biến mất. Luồng giao tiếp ngoài runtime dựa trên Push API và Service Worker.
Nói ngắn gọn, Cordis quản lý mỗi plugin bằng hai cơ chế phối hợp: dependency topology xác định fiber nào phải unload hoặc load lại khi provider thay đổi, còn effect stack của từng fiber chạy các disposer theo thứ tự ngược để thu hồi tài nguyên mà plugin đã tạo ra. Có thể hình dung đây là sự kết hợp giữa dependency graph và một stack dọn dẹp thông thường, được runtime nối thành lifecycle an toàn khi chương trình vẫn đang chạy.

Hình 6. Những primitive chính để đọc Cordis: plugin, context, service key, typed event, reversible effect và scope.
Vì vậy trong phạm vi bài viết này mình không xem Cordis chỉ là một cách rút gọn agent loop hay gom nhiều bước của ReAct vào một batch (điều mà framework Cordis hỗ trợ để tối ưu token). Cách gọi sát hơn là một plugin framework và runtime composition model dành cho hệ thống có topology thay đổi liên tục. Mục tiêu cuối cùng là để component có thể đi vào, đi ra, thay phiên bản và kéo theo dependency của nó một cách có phối hợp - nền tảng cần thiết nếu muốn agent harness thực sự self-evolving mà không phải restart cả process sau mỗi lần tự sửa..
Nếu mapping sang kiến trúc quen thuộc, Cordis gần với sự kết hợp giữa Generic Host/DI container của .NET và extension system của VS Code: một core ổn định, một contract chung, còn plugin được activate để đăng ký service, event, tool hay system prompt rồi deactivate để cleanup. ctx tương tự IServiceProvider/IServiceScope; fiber và effect gần với một plugin instance cùng chuỗi IDisposable/IAsyncDisposable mà nó sở hữu. Điểm khác là DI mặc định của .NET thường cố định sau Build(), còn Cordis cho phép provider xuất hiện hoặc biến mất ngay trong runtime; plugin phụ thuộc sẽ được dispose rồi kích hoạt lại theo dependency. Giống VS Code, plugin đã được build sẵn có thể được nạp hoặc tháo mà không cần build lại toàn bộ core. Có thể xem đây là dependency injection được mở rộng thành một plugin runtime có khả năng plug-in/plug-out và quản lý vòng đời.
Đối chiếu kiến trúc: .NET Dependency Injection · VS Code Extension Host
Hình 7. ASP.NET Core đăng ký dependency vào DI container trước khi gọi builder.Build(); sau thời điểm này container mặc định được xem như đã cố định.
PHẦN 03 — LIÊN KẾT ĐỘNG LÀM ĐƯỢC GÌ, VÀ CORDIS ĐI XA HƠN Ở ĐIỂM NÀO?
Phần trước nói về việc Cordis cho phép component xuất hiện, biến mất và được thay provider ngay khi chương trình đang chạy. Để thấy ý tưởng này kế thừa điều gì và bổ sung điều gì, ta cần quay lại một cơ chế đã có từ lâu trong lập trình hệ thống: liên kết thư viện.
Với C/C++, mã nguồn đi qua một chuỗi xử lý trước khi trở thành tiến trình đang chạy. Chi tiết có thể khác ở Java, .NET, JavaScript hay Python, nhưng điểm cần quan tâm trong bài này là thời điểm implementation được gắn với phần sử dụng nó.
Pipeline biên dịch và nạp chương trình
mã nguồn (source code) → trình biên dịch (compiler) → tệp đối tượng (object file) → trình liên kết (linker) → tệp thực thi (executable) → trình nạp (loader) → tiến trình đang chạy (running process)
Hình 8. Từ chương trình ngôn ngữ bậc cao qua hợp ngữ đến mã máy. Nguồn: slide Kiến trúc máy tính, UIT.
Thư viện (library) đóng gói một implementation sau contract đã thống nhất. Với liên kết tĩnh (static linking), mã cần thiết của thư viện được chép vào tệp thực thi tại build time. Muốn thay implementation, ứng dụng phải được liên kết và build lại.
Với liên kết động (dynamic linking), implementation nằm trong một tệp riêng, thường là DLL trên Windows hoặc shared object (.so) trên Linux/Unix. Trình nạp (loader) và trình liên kết động (dynamic linker) có thể gắn thư viện khi ứng dụng khởi động; chương trình cũng có thể chủ động nạp nó trong runtime bằng LoadLibrary() hoặc dlopen(). Phần sử dụng chỉ cần biết contract và cách gọi, còn implementation có thể được phân phối độc lập.
Tài liệu nền: CMU 15-213: Linking
Hình 9. Dynamic linker hoàn tất việc liên kết thư viện dùng chung khi chương trình được nạp. Nguồn: CMU 15-213.
Liên kết động giải quyết việc tách implementation khỏi tệp thực thi và trì hoãn thời điểm gắn kết, nhưng chưa tự quản lý vòng đời ở cấp kiến trúc. Một trình nạp DLL có thể đưa code vào bộ nhớ, nhưng nó không tự biết Notification Worker đang phụ thuộc vào Web Push Service, plugin đã tạo những timer hay listener nào, hoặc phải unload cả nhánh dependency theo thứ tự nào khi provider biến mất.
Lâp trình Web hiện đại cũng có 1 cơ chế tương tự ở ranh giới FE–BE. FE không chứa source code của BE mà phụ thuộc vào API contract; BE có thể đổi implementation hoặc redeploy độc lập miễn là contract vẫn tương thích. HTTP API không phải liên kết động theo nghĩa kỹ thuật—hai phía chạy ở những process khác nhau và giao tiếp qua network—nhưng cùng thể hiện một nguyên tắc: giữ contract ổn định để implementation có thể thay đổi độc lập.
Đến đây, mối liên hệ với Cordis trở nên rõ ràng hơn. Trình liên kết động chỉ đảm nhiệm việc tìm, nạp và gắn mã thư viện vào chương trình. Cordis mở rộng bài toán lên cấp component: xác định component nào được kích hoạt khi dependency được đáp ứng, component nào phải bị tháo khi provider biến mất, và những effect nào cần được dọn dẹp trong quá trình đó. Nói cách khác, liên kết động cho phép thay implementation của thư viện trong runtime; Cordis bổ sung dependency graph và cơ chế quản lý lifecycle để việc thay đổi giữa nhiều component diễn ra an toàn. Đây cũng là tư tưởng mình thử áp dụng vào CMS multi-tenant ở phần tiếp theo.
PHẦN 04 — ÁP DỤNG VÀO AGENT NATIVE CMS MULTI-TENANT
Các nền tảng như Spiderum hoặc Viblo là những CMS multi-tenant: nhiều người cùng đăng bài và mỗi người quản lý nội dung của mình. Người viết nhập Markdown hoặc dùng trình soạn thảo, còn CMS lưu nội dung và render bằng layout có sẵn. Mô hình này ổn định nhưng trải nghiệm của từng bài chỉ nằm trong những định dạng hệ thống đã hỗ trợ.

Hình 10. Landing page và feed của Agent Native CMS vẫn do kernel sở hữu để giữ trải nghiệm chung nhất quán.
Agent Native CMS thử đi xa hơn: coding agent không chỉ gửi content qua MCP mà còn viết một presentation program bằng TSX theo article SDK và upload nó như một plugin. Kernel giữ các capability ổn định như lưu trữ, biên dịch, phân quyền và audit log; program được kiểm tra rồi inject vào đúng article route, nơi nó có thể được activate, thay thế hoặc deactivate ngay trong runtime mà không cần dừng hay deploy lại kernel.
Điều này mở ra trải nghiệm đăng bài phong phú hơn layout cố định. Cùng một loại content, người dùng có thể muốn trình bày thành slideshow, infographic, longform hoặc một trải nghiệm kể chuyện riêng. Quan trọng hơn là các interactive elements: đây thường là điểm yếu của CMS truyền thống, vì mỗi interactive elements mới dễ trở thành một feature mà đội dev phải bổ sung vào core. Ở mô hình này, coding agent có thể tự tạo phần trình bày phù hợp với nhu cầu của từng bài mà CMS không phải phình ra để bao phủ mọi trường hợp.

Hình 11. Cùng một bài viết được trình bày như một slideshow tương tác, với từng slide, điều hướng và bố cục riêng do presentation program quyết định.

Hình 12. Một bài viết sử dụng presentation program được kích hoạt độc lập ngay trên article route.
Landing page, điều hướng và những phần UX chung vẫn thuộc quyền sở hữu của CMS kernel để giữ trải nghiệm đọc nhất quán và responsive. Phạm vi tùy biến chỉ nằm trong presentation của từng bài viết. Mỗi program được cô lập theo article và tenant; khi presentation hoặc bài viết bị xóa, artifact cùng những effect mà nó sở hữu phải được thu hồi sạch, không để lại dấu vết làm hỏng kernel hay ảnh hưởng tenant khác.
Nếu ánh xạ lại với các ví dụ phía trên: CMS kernel là stable core; article SDK là extension contract; presentation program gần với plugin; MCP là cổng để coding agent quan sát capability và gửi contribution; còn compile, activate, deactivate và cleanup tạo thành lifecycle. Tenant chỉ nắm tenant key, quyền admin được giữ ngoài agent và dùng qua giao diện quản trị, còn audit log ghi lại dòng sự kiện để toàn bộ hoạt động có thể được quan sát theo kiểu event stream.
Giao thức tích hợp: Model Context Protocol Specification
Hình 13. Kiến trúc hiện tại: coding agent đi qua MCP, operator đi qua admin boundary, còn CMS kernel giữ authentication, lifecycle và public delivery.
PHẦN 05 — ĐIỀU MÌNH MUỐN THẢO LUẬN
Mình chưa xem thử nghiệm này là một programming paradigm mới hay một Cordis hoàn chỉnh. Hiện tại nó mới là một mô hình kết hợp runtime (runtime composition model), trong đó coding agent có thể gửi cả content lẫn program vào vòng đời vận hành của CMS. Những điểm mình muốn đem ra thảo luận gồm:

Hình 14. Hướng phát triển: kernel ổn định, còn presentation artifact không đáng tin cậy được đưa vào một runtime có sandbox.
- Đoạn TSX do coding agent gửi lên nên được xem là content, plugin hay một đơn vị triển khai (deployment unit)?
- Nên quản lý source code, artifact, version và rollback như thế nào thay vì chỉ lưu code trong database rồi load lại?
- Micro-frontend có phù hợp với bài toán này hay cần một plugin runtime hoặc mô hình artifact riêng?
- Việc cho tenant truyền program vào runtime có vô tình tạo ra lỗ hổng remote code execution không?
- Ranh giới sandbox nào vừa đủ an toàn mà vẫn giữ được khả năng tùy biến và interactive elements?
Mình có hỏi AI về mấy câu này rồi, nhưng câu trả lời của AI mình thấy cũng chưa đáng tin lắm :V. Vì vậy mình rất muốn nghe góc nhìn của những bạn từng làm plugin system, CMS, SaaS multi-tenant, micro-frontend, application security hoặc agent harness.
Sản phẩm mình đã làm: Agent Native CMS
Cảm ơn mọi người đã đọc tới đây. Peace out ✌️
P/S: Sản phẩm sẽ tiếp tục được cập nhật cùng với hướng dẫn sử dụng. Bạn nào muốn trải nghiệm thì cứ liên hệ với mình để được cấp API key cho MCP server nhé. Hiện mình vẫn đang hoàn thiện cơ chế bảo mật nên chưa triển khai module đăng ký/đăng nhập công khai.
TÀI LIỆU THAM KHẢO
- DeepSeek Harness — source repository
- DeepSeek Harness Architecture
- Cordis Primer
- Capability Seams in DeepSeek Harness
- A Programming Paradigm for Spatiotemporal Composability
- ReAct: Synergizing Reasoning and Acting in Language Models
- .NET Dependency Injection
- VS Code Extension Host
- CMU 15-213: Linking
- Model Context Protocol Specification
All rights reserved




