Từ LLM đến HarnessPhần 5/11

Harness bắt đầu từ chỗ nào

Vòng lặp chỉ là bộ điều phối. Thứ biến nó thành harness là bốn mảnh cắm rời được, và trong một harness thật cả bốn đều là plugin thay được từ cấu hình.

Cây plugin của một harness với bốn mảnh lõi được tô đậm giữa các plugin khác
TL;DR — Vòng lặp chỉ là bộ điều phối. Bốn mảnh biến nó thành harness là sổ ghi bền, sổ đăng ký tool, đường ngân sách ngữ cảnh, và tầng quyền — và cả bốn đều phải cắm rời được.

Vòng lặp không phải harness

Bài trước kết thúc ở chỗ vòng lặp dài 101 dòng và phần dôi ra là những chỗ nó từng vỡ. Nhưng 101 dòng đó vẫn chưa phải một harness.

Vòng lặp làm đúng một việc: điều phối. Nó gọi model, chạy tool, lặp. Nó không biết lịch sử được ghi ở đâu, cũng không biết tool đến từ đâu. Nó không biết ai giữ ngân sách ngữ cảnh, và không biết ai cho phép một lời gọi chạy.

Bốn câu hỏi đó là bốn mảnh. Trong một harness thật, cả bốn đều là plugin — nghĩa là cả bốn đều thay được mà không sửa vòng lặp.

Bốn mảnh, và chúng nằm ở đâu

Trong harness tôi đọc, mỗi profile khởi động bằng cách nạp 94 dòng cấu hình plugin (gọi tắt là row) từ một bundle nền. Bốn mảnh cốt lõi nằm rải trong danh sách đó, mỗi mảnh mang một id riêng để có thể ghi đè (patch) qua cấu hình:

MảnhMục cấu hình (Row)Nhiệm vụ
Sổ ghi bềnsession, session-persistence-jsonlLưu mọi sự kiện model từng thấy
Sổ đăng ký tooltool-* (17 mục)Khai báo tool nào tồn tại
Ngân sách ngữ cảnhcompaction-basicGiữ request vừa cửa sổ
Tầng quyềnapproval, permission, sandboxQuyết định cái gì được chạy

Bốn cái tên, bốn mục cấu hình độc lập, không cái nào nằm cứng trong thân vòng lặp.

Đó là điểm mấu chốt. Vòng lặp chỉ gọi ctx.tools.execute() mà không cần biết tool đó là lệnh bash nội bộ hay một MCP server ở nửa vòng trái đất. Nó ghi sự kiện vào session mà không bận tâm session đang nằm trên RAM hay file JSONL dưới đĩa.

Thay một mảnh mà không sửa code

Đây là chỗ luận đề trở nên kiểm chứng được thay vì chỉ nghe hợp lý.

Mỗi mục cấu hình đều có một id. Muốn đổi hành vi của một plugin, bạn viết một bản vá (patch) trỏ vào đúng id đó. Bản vá sẽ thay toàn bộ cấu hình của plugin thay vì gộp lẫn lộn — nhờ vậy kết quả luôn xác định và không phụ thuộc thứ tự đọc file.

cordis.patch.yml — đổi một row, không chạm source
# Row gốc trong bundle chỉ có tên, không có config:
# - id: compaction-basic
# name: '@deepseek-ai/dsh-compaction-basic'
- id: compaction-basic
config:
thresholdRatio: 0.8 # mặc định của package
retainRatio: 0.16 # phần hội thoại gần nhất giữ nguyên văn

Hai khóa đó đều là tham số thật của package, và giá trị trên là mặc định của tác giả bundle. Cái bạn viết vào patch là của bạn.

Và tài liệu kiến trúc nói thẳng hệ quả:

There is no privileged core to patch: you extend dsh by mounting a plugin beside the others, and registrations are effects that unwind when their plugin unloads.— dsh, docs/architecture.md

Hai vế, và vế thứ hai quan trọng ngang vế thứ nhất. Plugin đăng ký thứ gì thì khi gỡ nó ra, thứ đó tự rút lại. Không còn tool mồ côi, không còn listener treo lại sau khi plugin chết. Đây là điều kiện để việc thay mảnh diễn ra lúc chạy, chứ không chỉ lúc build.

338 package, 70 tên tool

Bây giờ tới con số làm rõ cả bài.

Harness tôi đọc có 338 package trong workspace, trải trên 55 nhóm package, và 14.004 file được track. Nhưng bề mặt mà model nhìn thấy chỉ là 70 tên tool trong catalog.

Khoảng cách đó không phải lãng phí. Nó chính là định nghĩa của harness.

Một harness tốt giữ cho bề mặt hướng ra model nhỏ và ổn định, trong khi khối đứng sau lớn và thay đổi được. Model thấy một danh sách tool ổn định. Phía sau, mỗi tool trong đó có thể đến từ bundle nền, từ plugin ngoài cây, hoặc từ một MCP server. Model không phân biệt được, và không cần phân biệt.

Nếu bề mặt đó lớn lên theo khối đứng sau, bạn không có harness. Bạn có một đống package lộ ra ngoài.

Cây plugin của một harnessTừ 338 package workspace, một profile cắm 94 row plugin; bốn mảnh lõi nằm rải trong đó và đều thay được từ cấu hìnhProfile (web, headless, sdk)Bundle nền, 94 rowSổ ghi bềnSổ đăng ký toolNgân sách ngữ cảnhTầng quyền70 tên tool model thấy
Hình 1 — Cây plugin của một harness · tusgino
Xem dữ liệu diagram
type: architecture
title: Cây plugin của một harness
desc: Từ 338 package workspace, một profile cắm 94 row plugin; bốn mảnh lõi nằm rải trong đó và đều thay được từ cấu hình
nodes:
  - id: profile
    label: Profile (web, headless, sdk)
    kind: external
  - id: bundle
    label: Bundle nền, 94 row
    kind: input
  - id: log
    label: Sổ ghi bền
    kind: store
    focal: true
  - id: registry
    label: Sổ đăng ký tool
    focal: true
  - id: budget
    label: Ngân sách ngữ cảnh
  - id: perm
    label: Tầng quyền
    kind: security
  - id: surface
    label: 70 tên tool model thấy
    kind: external
edges:
  - [profile, bundle]
  - [bundle, log]
  - [bundle, registry]
  - [bundle, budget]
  - [bundle, perm]
  - [registry, surface]
  - [perm, surface]

Bốn profile, một bundle nền

Một chi tiết đáng chú ý: các profile không phải bốn bản sao. Chúng xếp lớp trên cùng một bundle nền — dsh-base, 94 row — rồi mỗi profile thêm phần của mình.

ProfileSố row riêngThêm gì
web112Ứng dụng trình duyệt
sdk-minimal32Cây SDK tối giản, cố ý không dùng bundle nền
sdk7Server JSON-RPC
headless5Runner một lần, không server
acp5Server tự động hoá

Nhìn headless: năm row. Một harness chạy một lần rồi thoát chỉ cần thêm năm dòng cấu hình lên trên bundle nền. Không có nhánh if (headless) nào trong vòng lặp.

Đó là bài kiểm tra tôi dùng cho mọi thiết kế harness: thêm một chế độ chạy mới tốn bao nhiêu dòng trong lõi? Nếu câu trả lời lớn hơn không, các mảnh chưa thật sự rời.

Làm được ngay

  1. Kể tên bốn mảnh trước khi viết vòng lặpChốt sổ ghi, sổ đăng ký, ngân sách, và quyền là bốn interface riêng. Nếu bạn chưa gọi tên được chúng, vòng lặp sẽ tự nuốt chúng.
  2. Cho mỗi mảnh một cái tên trong cấu hìnhMột id ổn định cho mỗi thành phần, để patch trỏ vào được. Không có tên thì không có chỗ để thay.
  3. Đo bề mặt model thấy, và giữ nó nhỏĐếm số tool và số trường model nhìn thấy. Con số đó nên tăng chậm hơn nhiều so với số package đứng sau.
  4. Thử gỡ một plugin xem có sạch khôngCắm rồi gỡ. Nếu còn tool mồ côi hay listener treo, việc đăng ký chưa phải một effect rút lại được.

Nguyên tắc

Vòng lặp là phần dễ nhất của một harness, và cũng là phần ít quyết định nhất. Thứ quyết định là bốn mảnh quanh nó có cắm rời được hay không.

Nếu bạn không thay được model adapter mà không sửa vòng lặp, bạn chưa có harness. Bạn có một vòng lặp với đèn báo.— tusgino
    • Vòng lặp chỉ điều phối; harness là sổ ghi, sổ đăng ký, ngân sách ngữ cảnh và tầng quyền.
    • Cho mỗi mảnh một id ổn định trong cấu hình, để thay hành vi bằng patch thay vì sửa mã nguồn.
    • Giữ bề mặt hướng ra model nhỏ và ổn định: 338 package đứng sau, 70 tên tool model thấy.
    • Đăng ký phải là một effect rút lại được, nếu không thì gỡ plugin sẽ để lại rác.
    • Phép thử cuối: thêm một chế độ chạy mới tốn bao nhiêu dòng trong lõi? Câu trả lời đúng là không dòng nào.

Câu hỏi thường gặp

Vòng lặp và harness khác nhau ở đâu?
Vòng lặp chỉ điều phối: gọi model, chạy tool, lặp. Harness là phần còn lại — nơi lịch sử được ghi bền, tool được đăng ký, ngân sách ngữ cảnh được giữ, và quyền được kiểm.
Vì sao phải tách thành plugin thay vì một khối?
Vì bạn cần thay một mảnh mà không chạm ba mảnh còn lại. Một khối liền buộc bạn sửa vòng lặp mỗi lần đổi model, đổi cách lưu, hay đổi chính sách quyền.
Có lõi đặc quyền nào phải vá không?
Trong harness tôi đọc thì không. Mọi thứ kể cả model adapter, sổ ghi và chính vòng lặp đều là plugin, nên mỗi thứ thay được từ cấu hình.
Bề mặt model thấy lớn cỡ nào?
Nhỏ hơn nhiều so với khối đứng sau. Trong harness tôi đọc: 338 package trong workspace, nhưng chỉ 70 tên tool mà model nhìn thấy.
Tú (tusgino)Tú (tusgino)

AI Software Engineer tại Đà Nẵng. Viết về kiến trúc hệ thống production và cái giá phải trả của từng lựa chọn.