·

Khi một AI agent phải xử lý một tác vụ lớn — ví dụ review toàn bộ một pull request 2.000 dòng, hay migrate một service từ REST sang gRPC — điều đầu tiên xảy ra trong context window (bộ nhớ ngữ cảnh) không phải là logic, mà là chi phí. Toàn bộ codebase liên quan, lịch sử hội thoại, kết quả tool call, log lỗi... tất cả bị nhồi vào một prompt duy nhất. Token bị tiêu tốn không phải vì agent "suy nghĩ" nhiều hơn, mà vì nó phải mang theo quá nhiều thứ không cần thiết cho từng bước suy luận cụ thể.

Task decomposition (phân rã tác vụ) là kỹ thuật chia một tác vụ lớn thành các tác vụ con nhỏ hơn, độc lập hơn, để mỗi tác vụ con chỉ cần một lượng context tối thiểu. Đây là chủ đề của bài học này: không phải decomposition như một pattern kiến trúc phần mềm kinh điển, mà decomposition như một chiến lược tối ưu token có thể đo lường bằng số liệu cụ thể.

Why Decomposition Is a Token Strategy, Not Just an Architecture Pattern

Trong kỹ thuật phần mềm truyền thống, chúng ta đã quen với việc chia nhỏ công việc: chia module, chia service, chia sprint, chia ticket. Lý do thường được đưa ra là để dễ quản lý, dễ test, dễ phân công cho nhiều người. Khi áp dụng cho AI agent, lý do đó vẫn đúng, nhưng có một lý do khác quan trọng hơn nhiều, mang tính kỹ thuật thuần túy: context window có giới hạn, và chi phí xử lý token không tuyến tính.

Ba cơ chế khiến decomposition trở thành một đòn bẩy tối ưu token trực tiếp:

1. Attention cost tăng theo cấp độ không tuyến tính. Cơ chế self-attention trong transformer có độ phức tạp tính toán xấp xỉ bậc hai theo chiều dài chuỗi (quadratic theo số token). Về mặt thực tế đối với người dùng, điều này thể hiện qua độ trễ (latency) tăng nhanh hơn khi context dài ra, và — quan trọng hơn với chủ đề bài này — qua hiện tượng "lost in the middle": khi context quá dài, model có xu hướng đánh giá thấp thông tin ở giữa prompt, dẫn đến chất lượng suy luận giảm dù số token đầu vào tăng. Nói cách khác, một context 50.000 token không "hiệu quả gấp 5 lần" so với 10.000 token — nó thường kém hiệu quả hơn.

2. Token không dùng vẫn phải trả tiền và vẫn ảnh hưởng đến chất lượng. Nhiều đội kỹ thuật lầm tưởng rằng "cứ nhồi hết context vào cho chắc" là an toàn. Thực tế, mỗi tool result, mỗi đoạn code không liên quan được đưa vào prompt đều làm tăng chi phí (cost) tính theo input token, tăng thời gian phản hồi, và làm loãng tín hiệu (signal) mà model cần để ra quyết định đúng. Đây chính là entropy nhiễu (noise) trong context.

3. Lỗi tích lũy trong agentic loop (vòng lặp agent). Một agent chạy trong loop nhiều bước — quan sát, suy luận, hành động, quan sát lại — nếu mang một context khổng lồ suốt cả loop, thì bất kỳ sai lệch nhỏ ở bước đầu (ví dụ hiểu sai một đoạn code) sẽ được "kế thừa" và khuếch đại qua từng bước sau, vì toàn bộ lịch sử sai đó vẫn nằm trong context. Khi decomposition tách biệt các subtask, lỗi ở một subtask không tự động lan sang subtask khác nếu boundary (ranh giới) được thiết kế tốt — nó bị "cách ly" giống như cách chúng ta cách ly lỗi bằng module boundary trong kiến trúc phần mềm.

Nói ngắn gọn: decomposition không chỉ giúp code sạch hơn, mà giúp mỗi lần gọi model chỉ phải "nhìn" đúng phần nó cần nhìn, và trả tiền đúng phần nó cần trả.

Mẹo

  • Trước khi tối ưu prompt hay cố gắng "nén" context bằng kỹ thuật summarization, hãy tự hỏi: tác vụ này có nên bị chia nhỏ trước không? Nhiều khi decomposition giải quyết vấn đề tận gốc, còn nén context chỉ là giải pháp vá tạm.
  • Coi context window giống như băng thông (bandwidth) hữu hạn của một pipeline CI/CD: bạn không đổ toàn bộ artifact của mọi service vào một job build duy nhất, bạn chia job theo service. Tư duy tương tự áp dụng cho agent.
  • Khi ước tính ROI của việc decomposition, đừng chỉ tính token tiết kiệm được — tính luôn cả tỷ lệ lỗi giảm được nhờ mỗi subtask có context "sạch" và tập trung hơn.

The Token Cost of Monolithic vs. Decomposed Tasks

Hãy so sánh cụ thể bằng số liệu, dùng một ví dụ thực tế: agent được giao nhiệm vụ "audit toàn bộ error handling trong một service Node.js có 40 file, sau đó viết báo cáo và đề xuất fix".

Cách làm monolithic (một tác vụ duy nhất, một context duy nhất):

Thành phần Số token ước tính
System prompt + instructions 800
Toàn bộ 40 file source code (trung bình 300 dòng/file) ~96.000
Lịch sử hội thoại + clarification qua lại 3.000
Kết quả các lần agent tự sửa sai (retry vì context quá lớn khiến model bỏ sót file) 12.000
Tổng input token cho một lần chạy hoàn chỉnh ~112.000
Output (báo cáo + đề xuất) 4.000

Với context lớn như vậy, nhiều model bắt đầu bỏ sót các file ở giữa danh sách (hiện tượng lost-in-the-middle), dẫn đến agent phải tự phát hiện thiếu sót và chạy lại một phần — đây là nguồn gốc của 12.000 token "retry" nói trên. Ngoài ra, chi phí không chỉ là tiền: độ trễ của một request 112.000 input token có thể lên đến hàng chục giây, ảnh hưởng trực tiếp đến trải nghiệm dùng agent trong CI hoặc trong IDE.

Cách làm decomposed (chia theo nhóm file logic, ví dụ 8 subtask, mỗi subtask 5 file liên quan chức năng):

Thành phần Số token ước tính (mỗi subtask)
System prompt + instructions cho subtask 400
5 file source code liên quan ~12.000
Context tóm tắt từ orchestrator (chỉ tên convention, không phải toàn bộ codebase) 300
Tổng input mỗi subtask ~12.700
Output mỗi subtask (finding + đề xuất fix cho nhóm file đó) 500

Nhân với 8 subtask: tổng input ≈ 101.600 token, nhưng không có retry vì mỗi context đủ nhỏ để model xử lý chính xác ngay lần đầu, và có thể chạy song song (parallel) nên tổng thời gian thực tế giảm gấp nhiều lần so với chạy tuần tự 112.000 token trong một request.

Sau đó cần một bước reduce (tổng hợp): orchestrator nhận 8 output (500 token × 8 = 4.000 token) và tổng hợp thành báo cáo cuối, tốn thêm khoảng 4.500 token input + 1.500 token output.

Tổng token toàn trình decomposed: 101.600 (map) + 4.500 (reduce input) + 1.500 (reduce output) ≈ 107.600 token, nhưng quan trọng hơn số token gần bằng, là:

  • Tỷ lệ lỗi giảm rõ rệt (không có hiện tượng bỏ sót file).
  • Không tốn token cho retry.
  • Thời gian wall-clock giảm mạnh nhờ chạy song song 8 subtask.
  • Mỗi subtask có thể dùng model rẻ hơn (ví dụ Haiku thay vì Sonnet) vì độ phức tạp suy luận trên 5 file thấp hơn nhiều so với 40 file, còn bước reduce mới cần model mạnh hơn để tổng hợp.

Đây chính là điểm mà nhiều đội kỹ thuật bỏ lỡ: lợi ích lớn nhất của decomposition không chỉ nằm ở tổng số token thấp hơn (đôi khi tổng số token gần tương đương), mà nằm ở việc mỗi token được chi tiêu hiệu quả hơn — model có context sạch, có thể dùng model rẻ hơn cho các subtask đơn giản, và loại bỏ vòng lặp sửa sai tốn kém.

Mẹo

  • Đo token thực tế bằng token counter của provider (ví dụ endpoint count_tokens của Anthropic) trước và sau khi decomposition, đừng chỉ ước lượng bằng cảm tính — số liệu thật thường gây bất ngờ.
  • Khi so sánh chi phí, luôn tính thêm chi phí retry/self-correction của phương án monolithic — đây là chi phí ẩn dễ bị bỏ quên khi lập bảng so sánh.
  • Nếu bước reduce (tổng hợp) cần suy luận phức tạp, hãy dùng model mạnh nhất cho bước đó và model rẻ hơn cho các subtask map — đây gọi là model tiering (phân tầng model theo độ phức tạp).

Decomposition Patterns: A Taxonomy

Không có một cách "chia nhỏ" duy nhất phù hợp cho mọi tác vụ. Dưới đây là bốn pattern (khuôn mẫu) phổ biến nhất khi thiết kế agentic workflow, mỗi pattern phù hợp với một dạng bài toán khác nhau.

Pattern 1: Sequential Pipeline (Map-then-Reduce)

Đây là pattern trong ví dụ token cost ở trên: tác vụ lớn được "map" thành N subtask độc lập chạy song song (hoặc tuần tự), sau đó kết quả được "reduce" — tổng hợp lại bởi một bước cuối.

Input lớn (40 files)
   │
   ├── Subtask 1: audit files [auth, session]      → finding_1.json
   ├── Subtask 2: audit files [db, orm]             → finding_2.json
   ├── Subtask 3: audit files [http, middleware]    → finding_3.json
   └── ... (chạy song song, mỗi subtask context nhỏ)
   │
   ▼
Reduce step: tổng hợp finding_1..N → final_report.md

Phù hợp khi: các phần việc độc lập với nhau, không cần biết kết quả của phần khác để làm phần mình (ví dụ audit từng module, generate test cho từng file, dịch từng chương sách).

Pattern 2: Hierarchical Decomposition (Tree)

Khác với pipeline tuyến tính, hierarchical decomposition chia tác vụ theo cấu trúc cây: một orchestrator cấp cao chia việc cho các sub-agent cấp trung, mỗi sub-agent cấp trung lại tiếp tục chia nhỏ hơn cho cấp dưới nếu cần.

Orchestrator: "Migrate service X từ REST sang gRPC"
   │
   ├── Sub-agent A: "Thiết kế proto schema"
   │      └── Sub-task A1: định nghĩa message cho domain Order
   │      └── Sub-task A2: định nghĩa message cho domain User
   │
   ├── Sub-agent B: "Viết server implementation"
   │      └── Sub-task B1: implement OrderService
   │      └── Sub-task B2: implement UserService
   │
   └── Sub-agent C: "Viết client migration + test"

Pattern này phù hợp với tác vụ có tính phân cấp tự nhiên — mỗi cấp chỉ cần biết đủ thông tin để giao việc cho cấp dưới, không cần "thấy" toàn bộ chi tiết triển khai ở lá cây (leaf node). Đây cũng chính là cách một orchestration layer (lớp điều phối) giữ context của mình gọn nhẹ: nó chỉ cầm interface/contract giữa các cấp, không cầm toàn bộ code.

Pattern 3: Speculative Parallel Decomposition

Đây là pattern ít được nói đến nhưng rất hiệu quả cho tối ưu token khi có sự không chắc chắn (uncertainty) về hướng đi đúng. Thay vì tuần tự thử một hướng, fail, rồi thử hướng khác (tốn token lặp lại toàn bộ context mỗi lần retry), ta chạy song song vài giả thuyết (hypothesis) với context tối thiểu riêng cho từng giả thuyết, rồi chọn kết quả tốt nhất.

Ví dụ: khi agent không chắc lỗi nằm ở tầng cache hay tầng database, thay vì điều tra tuần tự (context tích lũy dần qua mỗi lần thử), triển khai 2 sub-agent độc lập:

Speculative branch A: giả định lỗi ở cache layer
  → context: chỉ code liên quan cache (Redis client, TTL config)
Speculative branch B: giả định lỗi ở database layer
  → context: chỉ code liên quan DB (query, connection pool)

Cả hai chạy song song với context nhỏ, độc lập; branch nào tìm ra evidence (bằng chứng) xác thực thì được giữ lại, branch còn lại bị loại. Tổng token có thể cao hơn một chút so với việc "đoán đúng ngay từ đầu", nhưng thấp hơn nhiều so với điều tra tuần tự tích lũy context qua từng lần đoán sai, và nhanh hơn về wall-clock time.

Pattern 4: Checkpoint-and-Handoff

Pattern này áp dụng cho các tác vụ dài, nhiều giai đoạn, nơi agent cần "bàn giao" trạng thái công việc từ một context sang một context mới, tương tự việc một kỹ sư viết handover note trước khi bàn giao ca cho đồng nghiệp.

Ý tưởng: sau mỗi giai đoạn (checkpoint), agent không mang toàn bộ lịch sử suy luận sang bước tiếp theo. Nó chỉ tạo ra một artifact bàn giao (handoff artifact) — một bản tóm tắt có cấu trúc, súc tích — và context tiếp theo chỉ khởi tạo từ artifact đó, không phải từ toàn bộ lịch sử.

Giai đoạn 1: Phân tích yêu cầu
  → checkpoint artifact: requirements.md (structured, ~300 tokens)
Giai đoạn 2: Thiết kế API (chỉ nhận requirements.md, không nhận lịch sử chat giai đoạn 1)
  → checkpoint artifact: api_design.md (~400 tokens)
Giai đoạn 3: Implement (chỉ nhận api_design.md)

Đây là pattern cực kỳ quan trọng cho các agentic loop dài chạy trong nhiều giờ hoặc nhiều session — nếu không có checkpoint-and-handoff, context sẽ phình to tuyến tính theo thời gian chạy, và cuối cùng vượt giới hạn context window hoặc làm chi phí tăng vọt.

Mẹo

  • Chọn pattern theo tính chất phụ thuộc (dependency) giữa các phần việc: độc lập hoàn toàn → map-reduce; phân cấp tự nhiên → hierarchical; không chắc hướng đi → speculative; chạy dài nhiều giai đoạn → checkpoint-and-handoff.
  • Có thể kết hợp nhiều pattern trong cùng một workflow — ví dụ hierarchical ở tầng trên, nhưng mỗi sub-agent cấp trung lại dùng map-reduce nội bộ.
  • Đừng chọn speculative parallel decomposition nếu chi phí chạy song song (ví dụ gọi API tốn phí, hoặc rate limit thấp) cao hơn lợi ích tiết kiệm thời gian — pattern này đánh đổi token/cost lấy tốc độ và độ chính xác của lần thử đầu tiên.

Defining Subtask Boundaries: The Context Minimization Principle

Câu hỏi quan trọng nhất khi thiết kế decomposition không phải là "chia thành mấy phần", mà là "chia ranh giới ở đâu". Nguyên tắc cốt lõi ở đây là Context Minimization Principle (nguyên tắc tối thiểu hóa context): một boundary (ranh giới) subtask tốt là boundary sao cho lượng context cần truyền qua ranh giới đó là nhỏ nhất, trong khi vẫn đảm bảo subtask có đủ thông tin để hoàn thành chính xác.

Nói cách khác, hãy tìm những điểm cắt (cut point) trong bài toán nơi interface giữa hai phần hẹp hơn nhiều so với nội dung bên trong mỗi phần — giống hệt nguyên tắc "high cohesion, low coupling" (tính cố kết cao, độ kết dính thấp) trong thiết kế module phần mềm, nhưng áp dụng cho luồng thông tin giữa các lần gọi model.

Ba tiêu chí thực tế để xác định boundary tốt:

1. Information locality (tính cục bộ của thông tin). Nếu để hoàn thành subtask B, agent cần hiểu chi tiết toàn bộ logic bên trong subtask A, thì đó là dấu hiệu boundary đặt sai chỗ — hai subtask này lẽ ra nên gộp lại hoặc cần thiết kế lại điểm cắt. Ngược lại, nếu subtask B chỉ cần biết "kết quả" của A (ví dụ: A trả về danh sách file bị ảnh hưởng, B chỉ cần danh sách đó, không cần biết A đã phân tích thế nào), đó là boundary tốt.

2. Determinism của interface. Interface giữa hai subtask nên có định dạng xác định (structured), không mơ hồ. Nếu subtask A trả về output tự do dạng văn xuôi, subtask B sẽ phải "đọc hiểu" lại, tốn thêm token suy luận để trích xuất thông tin cần thiết — đây là lãng phí có thể tránh bằng cách ép A trả về JSON/YAML có schema rõ ràng.

3. Failure isolation (cách ly lỗi). Boundary tốt là boundary mà nếu subtask đó fail, phần còn lại của hệ thống vẫn có thể tiếp tục hoặc retry riêng subtask đó mà không phải chạy lại toàn bộ. Nếu một lỗi nhỏ ở subtask 3 buộc phải re-run cả pipeline từ subtask 1 (vì subtask 3 "biết" quá nhiều về nội bộ 1 và 2), thiết kế boundary đã thất bại.

Designing Output Artifacts for Handoff Efficiency

Chất lượng của decomposition phụ thuộc rất nhiều vào chất lượng của artifact bàn giao — thứ được truyền từ subtask này sang subtask kia (hoặc từ subtask lên orchestrator). Đây là phần dễ bị xem nhẹ nhất, nhưng lại là nơi token bị lãng phí nhiều nhất trong thực tế.

Nguyên tắc thiết kế artifact hiệu quả:

  • Structured over prose (có cấu trúc, không phải văn xuôi tự do). Dùng JSON, YAML, hoặc Markdown có heading rõ ràng thay vì để model tự do viết đoạn văn mô tả kết quả. Structured output vừa ít token hơn, vừa giúp subtask tiếp theo parse chính xác mà không cần suy luận thêm để hiểu ý.

  • Chỉ chứa "diff", không chứa "state" đầy đủ. Ví dụ, nếu subtask A đã đọc 5 file và chỉ 2 file có vấn đề, artifact bàn giao chỉ nên ghi 2 file có vấn đề kèm dòng lỗi cụ thể — không cần liệt lại toàn bộ nội dung 5 file đã đọc.

  • Có schema cố định qua các lần chạy. Nếu artifact có schema nhất quán (ví dụ luôn có field affected_files, risk_level, recommended_action), subtask sau có thể dùng prompt ngắn hơn để xử lý, vì không cần giải thích lại cấu trúc dữ liệu mỗi lần.

Ví dụ artifact tốt cho pattern map-reduce ở trên:

{
  "module": "auth",
  "files_reviewed": ["auth/session.ts", "auth/token.ts"],
  "findings": [
    {
      "file": "auth/token.ts",
      "line": 42,
      "issue": "Token expiry not checked before use",
      "severity": "high"
    }
  ],
  "recommended_fix_summary": "Add expiry validation in verifyToken()"
}

Artifact này chỉ khoảng 80-100 token nhưng đủ để bước reduce tổng hợp chính xác, so với việc để subtask viết một đoạn báo cáo văn xuôi dài 400-600 token chứa cùng lượng thông tin nhưng khó parse và dễ lặp từ.

Mẹo

  • Viết schema cho artifact bàn giao trước khi viết prompt cho subtask — thiết kế "hợp đồng dữ liệu" (data contract) trước, giống như thiết kế API contract trước khi code, tránh tình trạng mỗi subtask "tự do" trả về format khác nhau khiến bước sau phải suy luận thêm để hiểu.
  • Nếu phát hiện một subtask thường xuyên cần hỏi lại thông tin từ subtask trước (yêu cầu thêm context), đó là dấu hiệu artifact bàn giao đang thiếu field quan trọng — hãy bổ sung field đó vào schema thay vì cho phép subtask sau "xin thêm" toàn bộ context gốc.
  • Dùng validation (ví dụ JSON schema validation) để đảm bảo artifact đúng format trước khi truyền tiếp — artifact sai format là nguyên nhân phổ biến gây ra vòng lặp sửa lỗi tốn token ở bước sau.

Practical: Decomposing a Software Engineering Agent Task

Hãy áp dụng toàn bộ lý thuyết trên vào một tác vụ thực tế mà mọi kỹ sư đều quen: "Review và refactor một pull request lớn" — cụ thể, một PR thêm tính năng thanh toán (payment) gồm 18 file thay đổi, tổng cộng ~2.400 dòng diff, chạm vào 4 module: payment-service, order-service, notification-service, và shared-utils.

Bước 1 — Phân tích cấu trúc PR trước khi quyết định decomposition.

Trước khi chia việc, orchestrator (có thể là một agent nhẹ, dùng model rẻ) chạy một bước "quét nhanh" chỉ đọc danh sách file thay đổi và số dòng diff mỗi file (không đọc nội dung đầy đủ), để nhóm các file theo module liên quan. Bước này tốn rất ít token (~500 token) nhưng quyết định toàn bộ chất lượng decomposition sau đó.

git diff --stat origin/main...feature/payment-v2

Kết quả quét cho thấy:
- payment-service/: 7 file, ~1.100 dòng diff
- order-service/: 4 file, ~600 dòng diff
- notification-service/: 3 file, ~400 dòng diff
- shared-utils/: 4 file, ~300 dòng diff

Bước 2 — Chia thành 4 subtask review theo module (Pattern 1: map), mỗi subtask độc lập.

Subtask Nội dung Context đưa vào Token input ước tính Token output ước tính
1. Review payment-service Đọc diff + code liên quan, kiểm tra logic tính tiền, idempotency, xử lý lỗi thanh toán Diff 7 file + interface của order-service (chỉ signature, không full code) ~9.500 ~600
2. Review order-service Kiểm tra luồng tạo order, trạng thái order khi payment fail Diff 4 file + interface payment-service ~5.200 ~400
3. Review notification-service Kiểm tra template, retry khi gửi thông báo fail Diff 3 file (module này ít phụ thuộc module khác) ~3.100 ~300
4. Review shared-utils Kiểm tra thay đổi có breaking change không, có ảnh hưởng module khác đã dùng không Diff 4 file + danh sách nơi gọi hàm bị đổi (grep result, không phải full file) ~4.000 ~350

Chú ý: subtask 1 và 2 có phụ thuộc lẫn nhau (payment gọi order), nhưng thay vì cho cả hai đọc toàn bộ code của nhau, mỗi subtask chỉ nhận interface/signature của module kia (ví dụ chỉ định nghĩa function createOrder(orderInput): OrderResult, không phải toàn bộ implementation). Đây chính là áp dụng Context Minimization Principle đã nói ở phần trước.

Bước 3 — Mỗi subtask trả về artifact có cấu trúc:

{
  "module": "payment-service",
  "findings": [
    {
      "file": "payment-service/src/charge.ts",
      "line": 88,
      "issue": "No idempotency key check — duplicate charge risk on retry",
      "severity": "critical",
      "suggested_fix": "Add idempotency_key check before calling gateway.charge()"
    },
    {
      "file": "payment-service/src/webhook.ts",
      "line": 33,
      "issue": "Webhook signature not verified",
      "severity": "critical",
      "suggested_fix": "Verify HMAC signature using STRIPE_WEBHOOK_SECRET before processing"
    }
  ]
}

Bước 4 — Bước reduce: tổng hợp báo cáo cuối (orchestrator, dùng model mạnh hơn vì cần đánh giá ưu tiên toàn cục).

Orchestrator nhận 4 artifact (tổng ~1.650 token output từ 4 subtask) và tổng hợp thành báo cáo review cuối cùng, sắp xếp theo severity, đề xuất thứ tự fix. Bước này chỉ cần khoảng 2.200 token input (4 artifact + instruction tổng hợp) và 900 token output.

So sánh tổng thể:

  • Tổng token phương án decomposed: (9.500+5.200+3.100+4.000) input + (600+400+300+350) output + 2.200 (reduce input) + 900 (reduce output) ≈ 26.550 token, chạy song song 4 subtask nên thời gian thực tế chỉ bằng thời gian của subtask chậm nhất (~9.500 token) cộng bước reduce.
  • Phương án monolithic (đưa toàn bộ 2.400 dòng diff + toàn bộ 4 module vào một context): ước tính riêng phần diff và context liên quan đã ~28.000-32.000 token, cộng thêm rủi ro model bỏ sót vấn đề ở module ít "nổi bật" hơn (ví dụ shared-utils dễ bị lướt qua khi nằm cuối một context khổng lồ), dẫn đến khả năng phải review lại — kéo tổng chi phí thực tế lên 40.000+ token và thời gian chạy tuần tự lâu hơn nhiều.

Kết quả: decomposition ở ví dụ này không chỉ tiết kiệm khoảng 30-40% token, mà còn giảm đáng kể rủi ro bỏ sót lỗi nghiêm trọng (như thiếu kiểm tra idempotency key — một lỗi kinh điển trong hệ thống thanh toán) nhờ mỗi subtask có context tập trung, không bị pha loãng bởi các module không liên quan.

Mẹo

  • Luôn có một bước "quét nhanh cấu trúc" (structural scan) trước khi quyết định ranh giới decomposition — đừng đoán boundary dựa trên cảm tính, hãy dựa trên dữ liệu thực (số file, số dòng diff, dependency giữa module).
  • Với các module có phụ thuộc lẫn nhau, chỉ chia sẻ interface/signature, không chia sẻ toàn bộ implementation — đây là kỹ thuật giảm token quan trọng nhất trong ví dụ trên.
  • Dùng model rẻ cho bước quét/scan ban đầu và cho các subtask đơn giản (như review notification-service), dành model mạnh cho bước reduce cần đánh giá ưu tiên toàn cục — đây là model tiering áp dụng thực tế.

Decomposition Anti-Patterns to Avoid

Không phải cứ "chia nhỏ" là sẽ tối ưu token — chia sai cách có thể khiến kết quả tệ hơn cả không chia. Dưới đây là bốn anti-pattern (khuôn mẫu nên tránh) phổ biến nhất.

Anti-Pattern 1: Shallow Decomposition

Đây là kiểu chia "cho có", chia theo hình thức (ví dụ chia đều số file cho mỗi subtask, không quan tâm mối liên hệ logic) mà không dựa trên information locality thực sự. Kết quả: mỗi subtask vẫn cần biết về các subtask khác để làm đúng việc, buộc phải kéo thêm context từ ngoài vào, làm mất hết lợi ích của decomposition. Ví dụ: chia 18 file của PR thanh toán ở trên theo thứ tự alphabet thay vì theo module — subtask nào cũng có thể vừa dính file payment, vừa dính file notification, buộc phải load context của cả hai lĩnh vực trong mọi subtask.

Anti-Pattern 2: Passing Full Context Forward

Đây là lỗi khi thiết kế artifact bàn giao: thay vì trích xuất phần thông tin cần thiết, subtask sau nhận luôn toàn bộ output thô (raw output) — bao gồm cả lịch sử suy luận, cả những đoạn code đã đọc — của subtask trước "để cho chắc, tránh mất thông tin". Hệ quả là context tích lũy dần qua từng subtask, giống hệt vấn đề monolithic ban đầu nhưng bị "trì hoãn" thêm một vài bước. Đây là lỗi rất phổ biến khi đội kỹ thuật mới làm quen với multi-agent system: sợ mất thông tin nên cứ "forward hết", quên rằng mục tiêu của decomposition chính là ngăn việc này.

Anti-Pattern 3: Too-Fine Decomposition

Ngược lại với shallow decomposition, đây là lỗi chia quá nhỏ. Mỗi subtask chỉ làm một việc cực kỳ đơn giản (ví dụ: một subtask riêng chỉ để kiểm tra "file này có import đúng chưa"), nhưng overhead của việc khởi tạo context, system prompt, instruction cho mỗi subtask lại lớn hơn chính công việc thực sự cần làm. Nếu system prompt + instruction cho mỗi subtask tốn 400-500 token nhưng nội dung công việc chỉ đáng 50 token, tỷ lệ overhead trên tổng token có thể vượt 80-90% — decomposition lúc này phản tác dụng. Ngoài ra, chi phí điều phối (orchestration overhead) — số lần gọi API, độ trễ mạng, chi phí quản lý trạng thái giữa các subtask — cũng tăng theo số lượng subtask.

Anti-Pattern 4: Context Leakage Through Shared Memory

Trong nhiều framework multi-agent, các sub-agent chia sẻ một "bộ nhớ chung" (shared memory/state store) để tiện trao đổi. Vấn đề xảy ra khi shared memory không có ranh giới truy cập rõ ràng: một sub-agent viết một lượng lớn dữ liệu trung gian (ví dụ toàn bộ log debug, toàn bộ kết quả trung gian) vào shared memory, và framework tự động "tiêm" (inject) toàn bộ hoặc phần lớn shared memory đó vào context của sub-agent tiếp theo — dù sub-agent đó không cần đến phần lớn dữ liệu ấy. Đây là "context leakage" (rò rỉ context): context vốn được thiết kế để tách biệt lại bị nối lại một cách ngầm định qua lớp hạ tầng chia sẻ trạng thái, khiến lợi ích của decomposition bị vô hiệu hóa mà đội phát triển không hề nhận ra, vì "về lý thuyết" các subtask vẫn tách biệt.

Mẹo

  • Luôn đo token thực tế của từng subtask sau khi triển khai decomposition — nếu overhead (system prompt + instruction) chiếm quá 30-40% tổng token của subtask, xem xét gộp subtask đó với subtask liên quan.
  • Với shared memory/state store trong multi-agent framework, luôn thiết lập rõ scope truy cập: sub-agent chỉ đọc được phần dữ liệu mà nó thực sự cần, không đọc toàn bộ store theo mặc định — coi đây như access control (kiểm soát truy cập) áp dụng cho context, không chỉ cho dữ liệu.
  • Định kỳ review lại boundary decomposition khi codebase hoặc yêu cầu nghiệp vụ thay đổi — một boundary tốt ở thời điểm thiết kế ban đầu có thể trở thành shallow decomposition sau khi module đã tách ra theo hướng khác.

Summary

Task decomposition, khi được thiết kế đúng, là một trong những công cụ tối ưu token mạnh nhất cho AI agent — không phải vì nó luôn giảm tổng số token tuyệt đối, mà vì nó giúp mỗi token được chi tiêu vào đúng phần việc cần suy luận, loại bỏ vòng lặp sửa sai do context quá tải, cho phép chạy song song để giảm thời gian, và cho phép dùng model rẻ hơn ở những phần việc đơn giản.

Bốn pattern chính — sequential pipeline (map-reduce), hierarchical decomposition, speculative parallel decomposition, và checkpoint-and-handoff — mỗi pattern phù hợp với một dạng phụ thuộc công việc khác nhau. Nguyên tắc xuyên suốt khi xác định boundary là Context Minimization Principle: ranh giới tốt là ranh giới có interface hẹp, thông tin bàn giao có cấu trúc rõ ràng (structured artifact), và lỗi ở một subtask không lan sang subtask khác.

Cuối cùng, cần cảnh giác với bốn anti-pattern thường gặp: chia hình thức không dựa trên information locality (shallow decomposition), forward toàn bộ context thay vì trích xuất (passing full context forward), chia quá nhỏ khiến overhead vượt quá lợi ích (too-fine decomposition), và context bị rò rỉ ngầm qua shared memory (context leakage). Nắm vững những nguyên tắc và cạm bẫy này chính là nền tảng để thiết kế agentic workflow vừa chính xác, vừa tiết kiệm chi phí ở quy mô sản xuất thực tế.